創刊準備号 · 2026年秋English · RSS
AgentKoji

hooksが動かないときの確認

jqが無いhookがexit 0で終わり、rm -rf /を通しました。動かないhookの原因と確かめ方を、再現コマンドつきでまとめます。

要点(TL;DR)

  • 発火していない 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 が読み込まれているか

設定ファイルの置き場所が違う

ファイル範囲
~/.claude/settings.jsonすべてのプロジェクト
.claude/settings.jsonこのプロジェクト。Git に入れて共有できる
.claude/settings.local.jsonこのプロジェクト。自分だけ

hook は、これらの階層で上書きされず足し合わされます(出典:https://code.claude.com/docs/en/hooks 、2026-10-19確認)。(再現していない)

JSON の書式が壊れている

ダミーの .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

イベント名のつづりが違う

PreToolUSE と書いたところ、そのイベントは無視され、正しい名前の一覧が出ました。

- <project>/.claude/settings.json › hooks.PreToolUSE: Unknown hook event "PreToolUSE" was ignored. Valid events: PreToolUse, PostToolUse, PostToolUseFailure, ...

matcher がツール名と合っていない

小文字の matcher は claude doctor では見つかりません。 イベント名が正しく "matcher": "bash" のときは、何も指摘されませんでした。正規表現の動きは Node で確かめました。

$ node -e 'console.log(/Edit.*/.test("NotebookEdit"), /^Edit$/.test("NotebookEdit"))'
true false

実際のセッションで bash の matcher が発火しないかどうかは、(再現していない)。

変更が反映されていない、フォルダーが信頼されていない

スクリプトが起動しているか

スクリプトに実行権限がない

$ 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

パスが相対になっている

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 が入っていない

今回いちばん危なかったのがこれです。動いている 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 で終わっている

Blocked: rm
exit=1

公式は、0 と 2 以外の exit code は、1 も含めて止めないエラーとしています(同じ出典)。実際のセッションでツールが動くところは、(再現していない)。

理由を stdout に書いている

$ ... | .claude/hooks/stdout.sh 1>/dev/null
exit=2

何も表示されず、stderr が空だと分かりました。

hook がタイムアウトしている

JSON の判断が無視される

デバッグログを読む

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実践にあります。

まとめ

関連記事