Claude Code hooksが動かない・発火しない原因を、公式トラブルシュートとGitHub Issueの実例から全部洗い出した
Claude Codeのhooksを設定したのに動かない・発火しない原因を、公式ドキュメントの診断手順とGitHub Issueの実例をもとに整理。matcher・JSON構文・実行権限から、silentに死ぬ既知の不具合パターンまで症状別にまとめた。
エンジニアのゆとです。
settings.jsonにPreToolUseのhookを書いて、/hooksでも表示されてる。なのに実際にツールを呼んでも何も起きない。エラーも出ない。ログにも残らない——これがhooksの一番厄介なところだと思う。設定ミスなら大体エラーが出るからまだ潰しやすいんだけど、「何も起きずに黙って無視される」パターンが混ざってるせいで、正しく書けてるはずなのに動かない、という沼にハマる。
自分も一度、Edit|Writeにガード用のhookを仕込んで安心してた案件があって、後から気づいたら数十回分のEdit呼び出しでそのhookが一度も走ってなかったことがあった。設定は合ってたし、他のhook(Bashにマッチさせてたやつ)は普通に動いてた。原因を追ったら、動いてない方だけ実行権限が抜けてた、という単純な話だったんだけど、エラーが一切出ないから気づくまでに時間がかかった。
「hooks 動かない」で検索すると、公式ドキュメントのトラブルシュート項目と、有志がまとめた診断チェックリストがいくつか出てくる。どれも内容は正確なんだけど、「そもそもClaude Code側の既知の不具合でsilentに死ぬケースがある」という話までカバーしてる記事は少なかった。この記事では、公式の診断手順を軸にしつつ、GitHub Issueで実際に報告されている「設定は合ってるのに動かない」系の事例も並べて整理する。
まず結論 — /hooksと手動実行で「設定ミス」か「それ以外」かを切り分ける
hooksが動かないときにやることは、大きく2段階に分かれる。
- 設定側の原因を潰す(matcher・JSON構文・実行権限・イベント名の綴り)
- それでも直らないならClaude Code側の既知の不具合を疑う(バージョン依存のバグ、権限モードとの組み合わせ、部分的な設定破損)
多くの記事は1で止まっていて、2の存在を前提にしていない。でも実際、後述するGitHub Issueのように「他の人の環境では動くのに自分のセッションだけ動かない」「Bashにマッチさせたhookは動くのにEdit|Writeにマッチさせたhookだけ動かない」という、設定を何度見直しても原因が見つからない系の報告が継続的に上がっている。切り分けを急がず、まず1を機械的に潰してから2に進むのが結局一番早い。
Step1: 設定ミス側を機械的に潰す
/hooksで本当に登録されているか確認する
一番最初に見るべきはここ。settings.jsonを編集しても、ファイルウォッチャーが変更を拾えていなくて/hooksに反映されていないことがある。編集後に数秒待っても表示が変わらないなら、セッションを再開して強制的にリロードするのが早い。
編集しているファイルの場所自体を間違えているパターンも多い。hookのスコープは置く場所で決まる。
| 場所 | スコープ | 共有可否 |
|---|---|---|
~/.claude/settings.json | 全プロジェクト | 不可(マシンローカル) |
.claude/settings.json | 単一プロジェクト | 可(リポジトリにコミット) |
.claude/settings.local.json | 単一プロジェクト | 不可(gitignore対象) |
Plugin の hooks/hooks.json | プラグイン有効時 | 可(プラグインにバンドル) |
| Skill/agentのfrontmatter | そのSkill/agentがアクティブな間 | 可(コンポーネントファイルで定義) |
「チームの他のメンバーは動くのに自分だけ動かない」という相談は、大体この置き場所の勘違いか、.claude/settings.local.json側だけに書いていてリポジトリに含まれていないパターンのどちらかだった。
matcherは大文字小文字を区別する
bashとBashは別物として扱われる。ツール名マッチャーは正確なツール名(Bash、Edit、Write、Readなど)と一致させる必要があって、Edit|Writeのように|で複数指定するか、v2.1.191以降ならEdit, Writeのようにカンマ区切りでも書ける(|と,は相互に交換可能なリスト区切り文字になった)。
イベントによってmatcherがフィルタする対象も違う。PreToolUse・PostToolUse系はツール名、SessionStartは起動理由(startup/resume/clear/compact)、ConfigChangeは設定タイプ(user_settings/project_settingsなど)とバラバラなので、「イベント名は合ってるのにmatcherの値が別イベント用のものだった」という取り違えも実際にある。
JSONの構文エラー・末尾カンマ
settings.jsonはJSONそのものなので、末尾カンマやコメントは許可されない。/hooksにhooksが1件も表示されないなら、まずJSONとして壊れていないかを疑う。claude doctor(対話モード内では/doctor)を実行すると設定ファイルのパースエラーをまとめて診断してくれるので、目視で追うより先にこれを叩いたほうが速い。
スクリプトに実行権限がない・パスが解決できない
自分がハマったのがこのパターン。hookで指定したシェルスクリプトにchmod +xを忘れていると、静かに失敗する。
chmod +x .claude/hooks/protect-files.sh
「command not found」がトランスクリプトに出る場合は、相対パスの解決基準がClaude Codeを起動したディレクトリになっていて、想定と違う場所を指してしまっているケースが多い。絶対パスで書くか、プロジェクトルート基準の$CLAUDE_PROJECT_DIRを使うのが安全。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh"
}
]
}
]
}
}
hookスクリプトが単体で正しく動くかは、標準入力にサンプルJSONを流して手動テストすれば切り分けられる。
echo '{"tool_name":"Bash","tool_input":{"command":"ls"}}' | ./my-hook.sh
echo $? # 終了コードを確認
jq: command not foundが出る場合はjq自体が未インストール。brew install jq(macOS)かapt-get install jq(Debian/Ubuntu)で入れるか、JSON解析をPython/Node.jsに寄せる。

Step2: それでも動かないなら「Claude Code側の不具合」を疑う
Step1を全部潰しても直らない場合、設定ではなくClaude Code本体側の挙動を疑ったほうがいい。実際にanthropics/claude-codeのIssueには、「設定は完全に正しいのにsilentに死ぬ」系の報告が複数上がっている。どれも共通しているのは、エラーが一切出ないという点だ。
1つの壊れたエントリが同じイベントの全hookを道連れにする
Issue #82618で報告されている事例。UserPromptSubmitのhooks配列に、{"matcher": "", "hooks": [...]}というラッパー構造を省略した壊れたエントリを1つ追加しただけで、同じイベントに登録されていた他の正常なhookまで含めて全滅した。それまで50時間・88回連続で成功していたhookが、壊れたエントリを足した直後から158回連続で一度も発火しなくなり、アプリの再起動やマシンの再起動でも直らず、壊れたエントリを手動で修正した瞬間に次のメッセージから即座に復活したという。
エラーもログも一切出ないため、発見までに正常なエントリ同士を1つずつ見比べる作業が必要だったと報告されている。複数人・複数ツールでhooksの設定を追記していく運用をしている場合、これが刺さりやすい。
bypassPermissionsモードでmatcherの一部だけ発火しなくなる
Issue #74942。permissionMode: bypassPermissionsで動かしていたセッションで、Edit|WriteにマッチさせたPreToolUseのガードhook(編集回数の上限を超えたらdenyする安全装置)が、セッション全体で一度も呼ばれなかった。一方、同じセッション内でBashにマッチさせた別のhookは97回正常に発火していた。同じ設定ファイルの同じhooksブロック内で、片方だけ死ぬという報告になっている。
bypassPermissionsはあくまで権限プロンプトをスキップするだけで、hooksをスキップする仕様ではない——というのが公式の建て付けなので、この挙動が出たら設定ミスを疑う前に一度バージョンを疑ったほうがいい。
特定の作業ディレクトリだけhooksブロックが無視される
Issue #85430。同じ.claude/settings.json内で、permissions.denyルールは正常に効いているのに、hooks.PreToolUseだけが完全に無視される、という報告。denyが効いているということは設定ファイル自体は読み込まれているので、「ファイルが読まれていない」以外の原因でhooksブロックだけが死ぬケースがあることを示している。
subagent経由のツール呼び出しは親セッションのhookを素通りする
これは不具合というより仕様に近いが、知らないとハマる。Task toolで起動したsubagentが実行するBash・Edit・Write・Read・Grepの呼び出しは、親セッションに設定したPreToolUse/PostToolUseのhooksをスキップする。危険なコマンドのブロックや監査ログのような「絶対に全部通したい」hookを組んでいる場合、subagent経由の操作だけが素通りしてしまう可能性があるので、subagentを使うワークフローではhooksだけに安全装置を寄せないほうがいい。

トランスクリプトにエラーが出るパターンの対処
「発火はしてるけどエラーになる」場合は、ここまでとは別の切り分けになる。トランスクリプトにPreToolUse hook error: ...のようなメッセージが出ているなら、スクリプトが予期せず非ゼロの終了コードで終わっている。
シェルクォーティングを完全に避けたい場合は、"args": []を追加してexec formに切り替えると、シェルを経由せずスクリプトを直接起動できる。
JSON出力がおかしくなる意外な原因として、シェルの起動プロファイルが挙げられる。macOS/Linuxではsh -c、Windowsではデフォルトで Git Bash がコマンドhookを実行するが、Git Bashや一部の設定(BASH_ENVが~/.bashrcを指しているなど)は非対話シェルでもプロファイルをソースすることがある。プロファイル内に無条件のechoが書かれていると、その出力がhookのJSON出力の前に混入し、パースエラーになる。
Shell ready on arm64
{"decision": "block", "reason": "Not allowed"}
対処はプロファイル側のechoを対話シェルのみに限定すること。
# ~/.zshrc または ~/.bashrc 内
if [[ $- == *i* ]]; then
echo "Shell ready"
fi
$-はシェルフラグを保持する変数で、iが含まれていれば対話シェル。hooksは非対話シェルで実行されるので、この条件分岐を入れておけば余計な出力が混ざらなくなる。
Stopフックが8回ブロックで強制終了される
「無限ループで作業させ続けたいのに、8回目くらいで突然止まる」というのは不具合ではなく仕様。Claude CodeはStopフックが進捗なしで8回連続ブロックすると、それをオーバーライドして強制的にターンを終了する。フック側でstop_hook_activeフィールドを見て、すでにトリガー済みなら早期リターンする実装にしておく必要がある。
#!/bin/bash
INPUT=$(cat)
if [ "$(echo "$INPUT" | jq -r '.stop_hook_active')" = "true" ]; then
exit 0 # Claudeが停止することを許可
fi
# ... hookロジックの残り
正当に8回以上の反復が必要な設計なら、CLAUDE_CODE_STOP_HOOK_BLOCK_CAP環境変数で上限自体を引き上げられる。
本気で切り分けるなら--debug-fileでフル実行ログを見る
トランスクリプトビュー(Ctrl+Oで切り替え)は各hookの発火を1行サマリーで見せてくれるだけなので、「どのhookがマッチしたか」「終了コードは何か」「stdout/stderrの中身」まで追いたいなら、デバッグログを直接読むのが確実。
claude --debug-file /tmp/claude.log
別ターミナルでtail -f /tmp/claude.logを実行しておけば、hookの発火・終了コード・出力をリアルタイムで追える。すでに起動済みのセッションなら、/debugを実行すればログを有効化してパスを教えてくれる。
それでも原因がつかめない場合の最終手段は、自分の設定を全部外した状態と比較すること。
# CLAUDE.md・skills・plugins・hooks・MCPサーバーを全部無効化して起動
claude --safe-mode
--safe-modeで症状が消えるなら、原因はCLAUDE.md・skills・plugins・hooks・MCPサーバーのどれかにある。1つずつ戻して犯人を特定すればいい。MCP側の接続トラブルとの切り分けで迷っている場合はこちらも合わせて見ておくと早い。

FAQ
hooksを設定したのに1回も発火した形跡がない。何から見ればいい?
順番は、/hooksで登録確認 → matcherの大文字小文字とツール名の正確な一致 → JSON構文(末尾カンマ)→ スクリプトの実行権限、の4点セット。ここまでで9割は原因が特定できる。それでも直らなければ本記事のStep2で挙げたような、Claude Code側の既知の不具合を疑うフェーズに入る。
hookのエラーは出るのに、何が悪いのか分からない
echo '{...}' | ./my-hook.shのようにサンプルJSONを直接流し込んで、スクリプト単体の終了コードと出力を確認するのが一番早い。Claude Code経由で毎回試すより切り分けが速い。
設定ファイルを直したのに反映されない
hookブロック自体はファイル編集で自動的に拾われることが多いが、数秒待っても/hooksに反映されないなら、ファイルウォッチャーが変更を見逃している可能性がある。セッションを再開すれば強制的にリロードされる。
EditとWriteだけhookが効かなくてBashは効く、みたいな偏った症状が出た
自分の設定ミスを疑うのが先だが、matcherを何度見直しても合っているなら、Issue #74942のような既知の不具合パターンに当たっている可能性がある。バージョンを最新にアップデートしてから同じ手順を再現し、それでも直らないならanthropics/claude-codeのIssueで同じ症状の報告を検索するか、自分で再現手順つきで報告したほうが早く解決に近づく。
hooksは「決定論的にClaude Codeの挙動を縛る」という性質上、静かに機能しなくなること自体がセキュリティ・品質管理上のリスクになりやすい。設定を書いて満足するのではなく、一度はechoで発火確認するところまでやっておくと、後から「実は数十回動いてなかった」に気づいて青ざめる事態を避けられる。