FileChanged / CwdChanged(ファイルチェンジド・シーダブリュディーチェンジド)

フック
FileChanged / CwdChanged
ファイルチェンジド・シーダブリュディーチェンジド
監視対象のファイルが書き換わった瞬間(FileChanged)と、Claudeが作業フォルダを移動した瞬間(CwdChanged)に自分の処理を割り込ませる、観察専用のフック2種。Claude本体の動きを止めることはできず、ログ追記や別ツールの起動といった副作用に使う

direnvや.envrcを使ったことがあるClaude Code利用者向け

Claudeが.envや.envrcを書き換えた瞬間に裏でdirenv allowを打ちたい、cdでサブフォルダに入った瞬間にNode設定を切り替えたい、といった「反応型の自動運用」を仕込みたい場面で~/.claude/settings.jsonのhooksセクションに登録する

Claude Codeでセッション中に .env.envrc の中身を書き換えたとき、その変更にClaude本体が気づかず古い設定で動き続ける場面があります。FileChanged はその「ファイル更新の瞬間」を、CwdChanged は「Claudeが作業フォルダを移動した瞬間」を捕まえる、観察専用の仕掛けです。

direnv(ディレクトリ単位で設定値を切り替える定番ツール)と組み合わせて、Claudeが cd したら自動で direnv allow を裏で打つ、みたいな反応型の運用を作る時に効きます。私はこの2つを「セッション内の小さなwatcher」みたいな位置づけで使っています。

噛み砕くと「ファイル番人」と「移動番人」

FileChanged は「このファイルが書き換わったら教えて」と頼んでおく番人です。複数のファイル名を縦棒で並べて指定でき、いずれかが書き換わった瞬間に発火します。

CwdChanged は「Claudeが作業フォルダを移動したら毎回教えて」と頼んでおく番人です。こちらは番人を絞り込む指定が一切なく、移動するたびに必ず発火します。

どちらも「気づいたら自分の処理を裏で走らせる」用途で、Claude本体の動きをここで止めたりやり直させたりはできません。観察と副作用専用。これが2つの正体です。

大事な前提:両方とも「観察」しかできない

公式ドキュメントの判定表で、両方とも「No decision control. Used for side effects like logging or cleanup」と明記されています。記録を取る、別ツールを叩く、通知を出す、そういう副作用に使うものです。

もし「.env が更新された瞬間にClaudeを止めたい」みたいな制御をしたい場合、この2つでは無理です。PreToolUse のBash側でブロック判定を書く話になります。

「cooking-blog」プロジェクトで実演する

料理ブログを作るプロジェクトを想定します。~/projects/cooking-blog/ 直下に .env(公開向けの設定値)と .envrc(direnv用)があり、Claudeに記事の自動整形を任せている状況です。

ステップ1: ~/.claude/settings.json に2つのフックを登録する

ホーム直下の ~/.claude/settings.json にhooksセクションを足します。書式は他のフックと同じです。

{
  "hooks": {
    "FileChanged": [
      {
        "matcher": ".envrc|.env",
        "hooks": [
          {
            "type": "command",
            "command": "echo \"[hook] .env updated: $(date)\" >> ~/.claude/logs/envwatch.log"
          }
        ]
      }
    ],
    "CwdChanged": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "direnv export bash > /dev/null 2>&1 || true"
          }
        ]
      }
    ]
  }
}

FileChangedmatcher.envrc|.env と2つの名前を縦棒で並べてあります。これでどちらが書き換わっても発火します。CwdChanged 側には matcher 自体がありません。書いても無視されます。

ステップ2: Claudeに .env を書き換えさせる

Claudeに「.envBLOG_TITLE=cooking-blogBLOG_TITLE=世界の魚レシピ に書き換えて」と依頼します。Claudeは Edit ツールで保存し、その瞬間に FileChanged が発火します。

ログを確認します。

$ cat ~/.claude/logs/envwatch.log
[hook] .env updated: 2026-05-19 11:42:07

1行追加されています。書き換えに反応した証拠です。

ステップ3: Claudeに別フォルダへ移動させる

Claudeに「src/recipes/ の中を見て、空のテンプレを作って」と依頼すると、Claudeは内部で cd src/recipes を叩きます。この瞬間、CwdChanged が発火します。

続けて「../components/ の使い方も確認して」と頼むと、また cd が走って CwdChanged が発火します。短時間で2回連続で動く形です。

ステップ4: ここで初心者がやりがちな勘違い

FileChangedmatcher は他のフックと同じだろう」と思い込んで、*.envEdit のような書き方をしてしまうケースです。

公式ドキュメントは「The FileChanged event does not follow these rules when building its watch list」と例外を明示しています。FileChangedmatcher はファイル名そのものを縦棒で並べる、完全に独自の書式です。

ステップ5: 失敗パターンを直す

もし "matcher": "*.env" と書いてしまっていたら、ファイル名一覧として一致するものがなく、永遠に発火しません。設定は通るのにログが増えない、という事故になります。

正しくは ".env" 単独、または ".envrc|.env" のようにフルネームを並べる。これだけ覚えておけば事故は防げます。

ステップ6: hook側に渡ってくる情報を読む

hookスクリプト側では標準入力からJSONが渡ってきます。FileChanged なら file_pathchange の2つ、CwdChanged なら previous_cwdnew_cwd の2つ。bashスクリプトでJSONを読むなら jq を使うのが一番楽です。

#!/bin/bash
input=$(cat)
file=$(echo "$input" | jq -r '.file_path')
echo "[hook] changed: $file" >> ~/.claude/logs/envwatch.log

つまりこの2つは何をしてくれるのか

  • やってくれる: 設定ファイル更新と作業フォルダ移動の「瞬間」を捕まえ、自前の副作用処理を走らせる。ログ追記・別コマンドの起動・通知が中心
  • やってくれない: Claudeの動きを止める、やり直させる、書き換え内容を判定する、といった制御は一切できない。観察専用の判定表で「No decision control」と公式が明言している
  • 意味が薄い場面: 1セッション中にファイル更新も cd もまず起きない短時間の質問応答セッション。フックを足しても出番がないので、登録すら不要

使いどころ3シナリオ

シナリオ1: cooking-blogで .env 更新を必ずログに残したい

料理ブログを2人で運用していて、Claude経由で API_KEYBLOG_TITLE をいじることがある状況。FileChanged".env" を登録し、更新時刻と差分行を別ログに追記しておくと、後で「いつ誰が何を変えたか」がgitに残る前段階で押さえられます。

git管理外の .env でもログだけは残るのが地味に効きます。

シナリオ2: モノレポでサブプロジェクトに cd したら direnv を効かせたい

家計簿アプリのモノレポ(複数プロジェクトを1つの巨大フォルダで管理する形)で、フロント側に cd したら NODE_ENV=development、バックエンド側に cd したら NODE_ENV=production に切り替えたい場面。

CwdChangeddirenv export bash を仕込んでおくと、Claudeが内部で cd した瞬間に自動でdirenvが効きます。手動で都度切り替える手間がなくなります。

シナリオ3: OSSをcloneして .envrc が変わるたびにallowを促したい

新しいOSSをcloneして触り始めたばかりで、.envrc がプロジェクト直下にある状況。direnvはセキュリティ上、初回は手動で direnv allow を打たないと中身を読みません。

FileChanged.envrc を見張り、更新を検知したらターミナルに「direnv allow を打って」と通知を出す。安全寄りに止めつつ、見落としを防ぐ運用が組めます。

初心者が踏みやすい落とし穴

  • FileChangedmatcher にglobパターンを書いてしまう*.env**/.envrc は効きません。公式が「FileChanged はこのルールに従わない」と例外扱いしている書式です。正しくはファイル名そのまま、複数なら縦棒区切り
  • CwdChangedmatcher を書いて絞り込もうとする。公式ドキュメントに「no matcher support」「always fires on every directory change」と明記されています。書いても無視され、毎回発火するので、重い処理を仕込むとセッションが重くなる原因になります
  • CwdChanged で重い処理を走らせる。Claudeはサブフォルダ探索で cd を連発します。1秒かかる処理を仕込むとフォルダ移動5回で5秒固まる、みたいな体感になる。軽い処理だけ仕込むのが鉄則
  • exit code 2 で止めようとする。両方とも判定表で「No」「Shows stderr to user only」となっています。エラー終了させてもユーザー画面に標準エラー出力が出るだけで、Claude本体の動きは止まりません
  • FileChanged がgitのcheckout由来の更新も拾う。Claude自身が書き換えた時だけでなく、ブランチ切り替えで .env の中身が変わった場合も発火対象です。発火元を区別したいなら、hookスクリプト側で直前の操作履歴を見る必要があります
  • 渡ってくるJSONの構造を取り違えるFileChangedfile_pathchangeCwdChangedprevious_cwdnew_cwd。同じ感覚で書くとjqの抜き出しキーが空文字になり、ログが「[hook] changed:」みたいに尻切れになります
  • direnv未インストールで CwdChangeddirenv コマンドを仕込む。直前まで動いていた環境を別マシンに引っ越したときに踏みがちです。|| true を末尾に足してエラーを握りつぶしておくと、移行時にClaudeが連鎖して止まらずに済みます

書き方

~/.claude/settings.json の hooks に登録する

{
  "hooks": {
    "FileChanged": [
      { "matcher": ".envrc|.env", "hooks": [{ "type": "command", "command": "..." }] }
    ],
    "CwdChanged": [
      { "hooks": [{ "type": "command", "command": "..." }] }
    ]
  }
}

やってみるとこうなる

入力

{
  "hook_event_name": "FileChanged",
  "file_path": "/Users/me/projects/cooking-blog/.env",
  "change": "modified"
}

{
  "hook_event_name": "CwdChanged",
  "previous_cwd": "/Users/me/projects/cooking-blog",
  "new_cwd": "/Users/me/projects/cooking-blog/src/recipes"
}

出力例

exit code 0 で正常終了。標準エラー出力に何か書いてもClaudeの動きは止まらず、ユーザー画面にメッセージとして表示されるだけ(公式判定表で「No decision control」と明記)

このページに出てきた言葉

FileChanged
監視対象に登録したファイルが書き換わった瞬間に発火する観察専用フック。matcherにはファイル名そのものを縦棒区切りで並べる独自書式
CwdChanged
Claudeが内部でcdして作業フォルダを移動した瞬間に必ず発火する観察専用フック。絞り込み指定は受け付けず、毎回発火する
matcher
フックを発火させる対象を絞り込む指定。FileChangedはファイル名一覧(例: <code>.envrc|.env</code>)、CwdChangedは受け付けない
.envrc
direnvが読みに来るためのテキストファイル。フォルダに入った瞬間に中身が自動反映される仕組み用
direnv
フォルダ単位で設定値を自動で切り替えてくれる古参ツール。プロジェクトAに入ったらA用、Bに入ったらB用、と勝手に入れ替わる
No decision control
フックの判定表に出てくる用語。「このフックではClaude本体の動きを止めたりやり直させたりはできない」という意味。観察と副作用専用
settings.json
Claude Codeの設定をまとめたJSONファイル。<code>~/.claude/settings.json</code> がユーザー全体用、プロジェクト直下の <code>.claude/settings.json</code> がそのプロジェクト専用

関連項目

公式ドキュメント

https://code.claude.com/docs/en/hooks

-

← 戻る