[Claude Code] CwdChanged / FileChanged hook で direnv を自動化する

[Claude Code] CwdChanged / FileChanged hook で direnv を自動化する

2026.03.26

Claude Code の CwdChanged / FileChanged フック:direnv連携で環境変数管理を自動化する

Claude Codeのv2.1.83で新たに追加された CwdChangedFileChanged フックイベントを使うと、
ディレクトリ移動やファイル変更をトリガーに処理を自動実行できます。
この記事ではdirenvと組み合わせて環境変数を自動でロードする処理を確認してみました。

背景:Claude Code のフックシステム

Claude Code には、特定のイベントが発生した際にシェルを実行する「フック(Hooks)」機能があります。

従来は以下のようなイベントが利用可能でした。

従来のイベント トリガー条件
PreToolUse ツール実行前
PostToolUse ツール実行後
Notification 通知送信時
Stop レスポンス完了時
SubagentStop サブエージェント完了時

いろいろなフックタイミングがありますが、ディレクトリ移動や
ファイル変更には対応していませんでした。

新しいフックイベント

今回追加された2つのイベントにより、ディレクトリ移動などの環境変化に反応できるようになりました。

イベント トリガー条件 主な用途
CwdChanged 作業ディレクトリが変わったとき direnv連携、SDK切り替え、仮想環境の有効化
FileChanged 指定ファイルが変更されたとき(matcherでフィルタ可能) .env変更時の環境再読み込み、設定ファイル変更の検知

Claude Code & direnv

direnv は、ディレクトリごとに異なる環境変数を自動でロード・アンロードするツールです。
各ディレクトリに .envrc ファイルを配置し、そこに cd するだけで環境変数が切り替わります。

通常のターミナルでは、cd するたびに direnv のシェルフックが発火し、環境変数が自動でロードされます。
しかし、Claude Code の Bash ツールは非インタラクティブシェルで実行されるため、
direnvのシェルフックが動作しません。

なので、結果として以下のような問題がおこります。

# Claude Code の Bash ツールで実行
% cd api/
% echo $DB_HOST
# → 空(環境変数がロードされていない)

# 毎回手動で環境変数をロードする必要がある
% set -a && source .env && [ -f .env.local ] && source .env.local && set +a
% echo $DB_HOST
# → localhost

モノレポ構成でパッケージごとに異なる環境変数を持つプロジェクトの場合、
この手動ロードが繰り返し発生します。

確認:モノレポでのdirenv

本記事で使用するサンプルプロジェクトは以下のようなモノレポ構成です。

my-project/
├── .env                  # ルート共通(DB接続情報等)
├── .env.local            # ルートローカル(Git管理外)
├── .envrc                # direnv設定

├── backend/              # バックエンドAPI
│   ├── .env              # API固有の設定(認証、外部サービス等)
│   ├── .env.local
│   └── .envrc

├── infra/                # インフラ(IaC)
│   ├── .env              # クラウド関連の設定
│   ├── .env.local
│   └── .envrc

└── e2e/                  # E2Eテスト
    ├── .env
    ├── .env.local
    └── .envrc

各パッケージの .envrc は同一構成で、4段階のオーバーライドを行います。

# backend/.envrc(他パッケージも同様)
dotenv ../.env        # 1. ルート共通環境変数
dotenv ../.env.local  # 2. ルートローカル環境変数
dotenv .env           # 3. パッケージ共通環境変数
dotenv .env.local     # 4. パッケージローカル環境変数(最優先)

この構成により、各パッケージは共通のDB接続情報を継承しつつ、固有の設定を追加しています。

パッケージ 固有の環境変数
ルート DB_HOST, DB_PORT, DB_PASSWORD
backend/ API_KEY, AUTH_DOMAIN, STORAGE_BUCKET
infra/ CLOUD_PROFILE, DEPLOY_REGION

Setup

1. hook script作成

% mkdir -p .claude/hooks

ディレクトリ移動時に direnv を実行する
.claude/hooks/on-cwd-changed.shシェルを作成します。

#!/bin/bash
# CwdChanged hook: direnvで環境変数を自動ロード
if command -v direnv &> /dev/null && [ -f .envrc ]; then
  direnv allow .envrc 2>/dev/null
  eval "$(direnv export bash)"
  direnv export bash >> "$CLAUDE_ENV_FILE"
fi

また、.env ファイル変更時に環境を再ロードする
.claude/hooks/on-env-changed.shシェルも作成。

#!/bin/bash
# FileChanged hook: .envファイル変更時に環境変数を再ロード
if command -v direnv &> /dev/null && [ -f .envrc ]; then
  eval "$(direnv export bash)"
  direnv export bash >> "$CLAUDE_ENV_FILE"
fi

実行権限を付与。

% chmod +x .claude/hooks/on-cwd-changed.sh .claude/hooks/on-env-changed.sh

2. settings.json にフックを登録

.claude/settings.local.json.claude/settings.jsonに以下を追加します。

{
  "hooks": {
    "CwdChanged": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/on-cwd-changed.sh",
            "async": true,
            "timeout": 10
          }
        ]
      }
    ],
    "FileChanged": [
      {
        "matcher": "^\\.envrc$|^\\.env$|^\\.env\\.local$",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/on-env-changed.sh",
            "async": true,
            "timeout": 10
          }
        ]
      }
    ]
  }
}

hook scriptのパスには $CLAUDE_PROJECT_DIR 環境変数を使った絶対パスを指定します。
hookは現在の作業ディレクトリで実行されるため、cd api/ 後にフックが発火すると
相対パス .claude/hooks/...api/.claude/hooks/... を探しに行って失敗します。
また、FileChangedmatcher は、変更ファイルの basename に対する正規表現(regex)です。

3. CLAUDE_ENV_FILE

CLAUDE_ENV_FILE は Claude Code が提供する特殊な環境変数で、ここに書き出された export 文が後続の Bash 実行に反映されます。SessionStartCwdChangedFileChanged の3つのフックイベントでのみ利用可能です。

参照

CLAUDE_ENV_FILE is available for SessionStart, CwdChanged, and FileChanged hooks.
Other hook types do not have access to this variable.

% direnv export bash >> "$CLAUDE_ENV_FILE"

direnv export bash の出力は以下のような形式です。

export DB_HOST=$'localhost';
export DB_PORT=5432;
export API_KEY=$'sk-dev-xxxxxxxxxxxx';
export AUTH_DOMAIN=$'dev.example.com';
# ...

この出力を CLAUDE_ENV_FILE に追記することで、Claude Code のシェル環境に環境変数が注入されます。

Test Results

フックなし(従来の動作)

操作 DB_HOST API_KEY 結果
ルートに移動 未設定 未設定 環境変数なし
cd backend/ 未設定 未設定 環境変数なし
cd infra/ 未設定 未設定 環境変数なし

Claude Code の Bash ツールではどこに移動しても環境変数は一切ロードされず、
毎回 set -a && source .env && ... の手動実行が必要。

フックあり(CwdChanged + direnv)

操作 DB_HOST API_KEY AUTH_DOMAIN 結果
ルートに移動 localhost 未設定 未設定 ルートの .env のみロード
cd backend/ localhost sk-dev-xxx... dev.example.com ルート + backend/ の .env がロード
cd infra/ localhost 未設定 未設定 ルート + infra/ の .env がロード

ディレクトリ移動だけで、そのパッケージに必要な環境変数が自動的に揃います。
backend/ に移動すれば認証関連の設定が、
infra/ に移動すればそこにある設定が自動でロードされます。

CwdChangedFileChanged はdirenv以外にも活用できます。
Nodeバージョンの自動切り替えをしたり、Python仮想環境の自動有効化したりなど、
いろいろ使えます。

# .claude/hooks/on-cwd-changed.sh
if [ -f .node-version ]; then
  NODE_VERSION=$(cat .node-version)
  export PATH="$HOME/.nvm/versions/node/v${NODE_VERSION}/bin:$PATH"
  echo "export PATH=\"$HOME/.nvm/versions/node/v${NODE_VERSION}/bin:\$PATH\"" >> "$CLAUDE_ENV_FILE"
fi
# .claude/hooks/on-cwd-changed.sh
if [ -f .venv/bin/activate ]; then
  source .venv/bin/activate
  echo "export PATH=\"$(pwd)/.venv/bin:\$PATH\"" >> "$CLAUDE_ENV_FILE"
  echo "export VIRTUAL_ENV=\"$(pwd)/.venv\"" >> "$CLAUDE_ENV_FILE"
fi

セキュリティ上の考慮

  • direnv allow を自動実行するため、信頼できるリポジトリでのみ使用してください
  • .envrc に悪意のあるコマンドが含まれていると、自動実行されるリスクがあります
  • チームで共有する場合は .claude/hooks/ をリポジトリに含め、settings.local.json は個人設定としてGit管理外にすることを推奨します

Reference

Summary

v2.1.81でClaude Code に追加された CwdChangedFileChangedのhookイベントについて確認してみました。
モノレポ構成のプロジェクトにおいては毎回 source .envをしていた環境変数設定が、
自動で設定されるようになりました。
これ以外にもパスによる設定切り替えが簡単にできるので、便利です。

この記事をシェアする

FacebookHatena blogX

関連記事