hooksが動かないときの確認
jqが無いhookがexit 0で終わり、rm -rf /を通しました。動かないhookの原因と確かめ方を、再現コマンドつきでまとめます。
- 発火していない hook と、発火して通した hook は外から同じに見えます。まず claude doctor を実行します。末尾のカンマとイベント名のつづり違いは、これで見つかりました。
- 設定ファイルが公式の置き場所にあるか(/status で確認)と、matcher が大文字・小文字まで正確なツール名と合っているかを確かめます。
- 小文字の matcher("bash")は claude doctor では指摘されません。/hooks で、読み込まれた hook と定義された場所を確かめます。
発火していない hook と、発火して「通してよい」と判断した hook は、外から見ると同じに見えます。この記事は、Claude Code の hook が何もしていないように見えるときに、編集部が確かめる項目をまとめたものです。各項目に、症状、確かめるコマンド、直し方を書きました。手元で再現できたものはコマンドと出力を載せ、再現していないものには「(再現していない)」と書いています。検証環境は macOS、Claude Code 2.1.303、jq 1.7.1 で、2026年10月19日に、ダミーのプロジェクトとダミーの JSON で確かめました。実際の Claude Code のセッションは動かしていません。正しく動く例と比べたいときはPreToolUse hookの例集を見てください。
まず claude doctor を実行する
claude doctor は、セッションを始めずに、インストールと設定の診断を表示します(出典:https://code.claude.com/docs/en/setup 、2026-10-19確認)。プロジェクトのディレクトリで最初に実行します。下の原因のうち2つは、これで見つかりました。
hook が読み込まれているか
設定ファイルの置き場所が違う
- 症状:あるプロジェクトでは動くのに、別のプロジェクトでは動かない。またはどこでも動かない
- 確かめ方:セッションで
/statusを実行する。Setting sourcesの行に、読み込まれた設定ファイルが並ぶ(出典:https://code.claude.com/docs/en/settings 、2026-10-19確認) - 直し方:公式の置き場所に置く
| ファイル | 範囲 |
|---|---|
~/.claude/settings.json | すべてのプロジェクト |
.claude/settings.json | このプロジェクト。Git に入れて共有できる |
.claude/settings.local.json | このプロジェクト。自分だけ |
hook は、これらの階層で上書きされず足し合わされます(出典:https://code.claude.com/docs/en/hooks 、2026-10-19確認)。(再現していない)
JSON の書式が壊れている
- 症状:そのファイルの hook がどれも動かない
- 確かめ方:ファイルを
jq .に通し、claude doctorも実行する - 直し方:末尾のカンマとコメントを消す。どちらも書けない
ダミーの .claude/settings.json に、末尾のカンマを残して試しました。
$ jq . .claude/settings.json
jq: parse error: Expected another key-value pair at line 4, column 3
exit=5
claude doctor も同じファイルを挙げました。
Invalid settings
- <project>/.claude/settings.json: Invalid or malformed JSON
イベント名のつづりが違う
- 症状:あるイベントの hook だけが動かない
- 確かめ方:
claude doctor - 直し方:イベント名を公式のドキュメントから写す
PreToolUSE と書いたところ、そのイベントは無視され、正しい名前の一覧が出ました。
- <project>/.claude/settings.json › hooks.PreToolUSE: Unknown hook event "PreToolUSE" was ignored. Valid events: PreToolUse, PostToolUse, PostToolUseFailure, ...
matcher がツール名と合っていない
- 症状:ほかのツールでは動くのに、狙ったツールでは動かない
- 確かめ方:matcher を正確なツール名と比べる。
Bash、Edit、Write、Read、mcp__<server>__<tool>など - 直し方:matcher は大文字と小文字を区別する。
Edit|Writeは完全一致の並びになる。英数字・_・-・空白・,・|以外の文字が入ると、前後を固定しない JavaScript の正規表現として扱われるので、Edit.*はNotebookEditにも当たる。完全一致にしたいなら^Edit$と書く(出典:https://code.claude.com/docs/en/hooks 、2026-10-19確認)
小文字の matcher は claude doctor では見つかりません。 イベント名が正しく "matcher": "bash" のときは、何も指摘されませんでした。正規表現の動きは Node で確かめました。
$ node -e 'console.log(/Edit.*/.test("NotebookEdit"), /^Edit$/.test("NotebookEdit"))'
true false
実際のセッションで bash の matcher が発火しないかどうかは、(再現していない)。
変更が反映されていない、フォルダーが信頼されていない
- 症状:hook を書き換えたのに何も変わらない
- 確かめ方:
/hooksを実行する。設定された hook と、それぞれがどこで定義されたかが読み取り専用で並ぶ - 直し方:設定ファイルの変更は、通常はファイルの監視で自動で読み込まれる。数秒たっても出てこなければ、セッションを再起動する。対話のセッションでは、ワークスペースの信頼の確認に同意するまで、設定ファイルの hook は動かない(出典:https://code.claude.com/docs/en/hooks 、https://code.claude.com/docs/en/hooks-guide 、2026-10-19確認)。(再現していない)
スクリプトが起動しているか
スクリプトに実行権限がない
- 症状:
hook errorの通知が出て、ツールはそのまま動く - 確かめ方:
ls -l .claude/hooks/ - 直し方:
chmod +x .claude/hooks/*.sh
$ echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /"}}' | sh -c .claude/hooks/block-rm-root.sh
sh: .claude/hooks/block-rm-root.sh: Permission denied
exit=126
パスが相対になっている
- 症状:Claude がサブディレクトリでコマンドを実行すると、hook が動かなくなる
- 確かめ方:hook の
commandを別のディレクトリから実行してみる - 直し方:
"$CLAUDE_PROJECT_DIR"/.claude/hooks/...とダブルクォートで囲んで書く。または"args": []を付けた exec 形式にする(出典:https://code.claude.com/docs/en/hooks-guide 、2026-10-19確認)
src/ から実行した結果です。
sh: .claude/hooks/block-rm-root.sh: No such file or directory
exit=127
126 も 127 も止めないエラーなので、ツールの呼び出しは進みます。hook が起動できないときに止めたいなら "onFailure": "block" を付けます(v2.1.295 以降。出典:https://code.claude.com/docs/en/hooks 、2026-10-19確認)。
jq が入っていない
- 症状:hook がすべてを通す
- 確かめ方:hook が動く環境で
which jq - 直し方:jq を入れる。または Python や Node で JSON を読む
今回いちばん危なかったのがこれです。動いている rm のガードを、PATH から jq を外して実行しました。
.claude/hooks/block-rm-root.sh: line 3: jq: command not found
exit=0
cmd=$(jq ...) が空になり、何にも当たらず、exit 0 まで進みました。エラーの通知も出ずに rm -rf / を通します。ガードのスクリプトは、最初に command -v jq で確かめ、無ければ exit 2 で止まるように書きます。
止める動きになっているか
exit 2 ではなく exit 1 で終わっている
- 症状:hook は「Blocked」と出すのに、コマンドが動く
- 確かめ方:サンプルの入力を流したあと
echo $? - 直し方:PreToolUse で止めるなら
exit 2
Blocked: rm
exit=1
公式は、0 と 2 以外の exit code は、1 も含めて止めないエラーとしています(同じ出典)。実際のセッションでツールが動くところは、(再現していない)。
理由を stdout に書いている
- 症状:止まりはするが、Claude に理由が伝わらない
- 確かめ方:stdout を捨てて hook を実行し、何が残るかを見る
- 直し方:exit 2 のときは、理由を stderr(
>&2)に書く。Claude Code は stderr を止めた理由として使う
$ ... | .claude/hooks/stdout.sh 1>/dev/null
exit=2
何も表示されず、stderr が空だと分かりました。
hook がタイムアウトしている
- 症状:遅い hook が飛ばされているように見える
- 直し方:command hook のタイムアウトの既定値は600秒で、
UserPromptSubmitでは30秒。タイムアウトした PreToolUse hook は、onFailureが"block"でない限り止めない(出典:https://code.claude.com/docs/en/hooks 、2026-10-19確認)。(再現していない)
JSON の判断が無視される
- 症状:正しい JSON を返しているのに効かず、エラーも出ない
- 直し方:
permissionDecisionは一番外ではなくhookSpecificOutputの中に置く。JSON より前に何も出力されていないかも確かめる。シェルの設定ファイルのechoがよくある原因で、{で始まらない出力はただのテキストとして読まれる(出典:https://code.claude.com/docs/en/hooks-guide 、2026-10-19確認)。(再現していない)
デバッグログを読む
claude --debug-file <path> で、hook の実行の詳細がファイルに書かれます。claude --debug の場合は ~/.claude/debug/<session-id>.txt に書かれます。v2.1.296 以降は、ツールのイベントの command hook が終わると、終了の状態と所要時間を含む Hooks: の行が残ります(出典:https://code.claude.com/docs/en/hooks 、2026-10-19確認)。2.1.303 の claude --help に両方のフラグがあることは確かめました。実際のセッションのログは読んでいません。(再現していない)
環境変数が hook に届かない問題は別の話で、Claude Codeで環境変数が効かないで扱っています。exit code がなぜ大事かはClaude Code hooks実践にあります。
まとめ
- 最初に
claude doctorを実行する。壊れた JSON とイベント名のつづりは見つかるが、小文字の matcher は見つからない - matcher は大文字と小文字を区別し、英数字・
_・-・空白・,・|以外の文字が入ると正規表現になる - 実行権限がない(126)、パスが違う(127)ときは、呼び出しが通る。
"$CLAUDE_PROJECT_DIR"とchmod +xで防ぐ - 止めるなら
exit 2で、理由は stderr に書く。exit 1では止まらない - jq が見つからないガードは exit 0 で全部を通しうる。最初に jq を確かめ、無ければ止める
- 何が読み込まれ、何が動いたかは
/hooksとclaude --debug-fileで見る