Claude Code のフックを理解する:エージェントを取り囲む確定的なレイヤー
Claude Code のフックとは? フックとは、ユーザーが定義するシェルコマンド(さらに HTTP エンドポイント、MCP ツール、モデルへのプロンプトも含みます)であり、Claude Code がライフサイクルの決まった地点で自動的に実行するものです。ツール呼び出しの前、編集の後、セッション開始時、Claude が応答を終えたとき、といったタイミングです。1 CLAUDE.md がモデルにおそらく従うであろう指示を与えるのに対し、フックはモデルが協力するかどうかにかかわらず実行されます。セッション内で /hooks と入力すれば、すべてのライフサイクルイベントと、それぞれに何が結び付けられているかを確認できます。
{.answer-block}
多くの開発者は、2つの制御レイヤーで Claude Code を運用しています。エージェントに何を許すかを制御する権限と、エージェントが何をすべきかを記述する CLAUDE.md です。フックは3つ目のレイヤーであり、何かを保証できる唯一のレイヤーです。以下では、考え方の枠組み、現行ドキュメントに載っているすべてのライフサイクルイベント、正確な入出力の取り決め、設定方法、動作する5つのパターン、そして判断の枠組みを扱います。API に関する詳細はすべて、2026年7月1日時点の公式フックリファレンスおよびガイドと照合して検証しています。このシステムは変化が速いため、本記事とリファレンスが食い違う場合はリファレンスが正です。(Claude Code は初めてですか? まずは5分でできるセットアップ、またはClaude Code 入門ガイドからどうぞ。)
要点: フックは stdin から JSON を受け取り、終了コードまたは stdout に出力する JSON で応答します。終了コード0は許可、終了コード2はブロック(ブロック可能なイベントの場合)を意味し、Unix で慣習的に失敗を表す終了コード1は何もブロックしません。これがフックで最も陥りやすい落とし穴です。2 設定は settings.json の中で、PreToolUse や Stop といったイベント名の下に記述し、マッチャーで絞り込みます。必ず起きなければならないことにはフックを使い、モデルが単に知っておけばよいことには CLAUDE.md を使いましょう。
考え方の枠組み:非決定的な中核を取り囲む保証
コーディングエージェントは確率的なシステムです。編集のたびに Prettier を実行するよう頼めば、たいていは実行してくれます。しかし、変更が些細に見えるとき、コンテキストが長くなったとき、あるいは指示の言い回しが違って受け取られたときには、その手順を飛ばすかもしれません。CLAUDE.md、スキル、プロンプトはいずれも提案にすぎません。質は高く、たいてい従われますが、決して保証はされないのです。
フックは、その中核を取り囲む確定的な殻です。公式の定義はこうです。「Claude Code のライフサイクルの特定の地点で自動的に実行される、ユーザー定義のシェルコマンド、HTTP エンドポイント、またはLLMへのプロンプト」であり、「Claude Code の挙動を確定的に制御し、LLMが実行を選ぶことに頼るのではなく、特定の動作が必ず起きるようにする」ものです。3 フォーマッターは編集のたびに発火します。コマンドガードはすべての Bash 呼び出しを評価します。完了ゲートはすべての終了時に検査します。
この強制は見せかけではなく本物です。PreToolUse フックはあらゆる権限モードの検査より前に発火するため、permissionDecision: "deny" を返すフックは、bypassPermissions モードや --dangerously-skip-permissions の下であってもツールをブロックします。ただし逆は成り立ちません。"allow" を返すフックでも、設定ファイルの拒否ルールを緩めることはできません。フックは権限が許す範囲を超えてポリシーを厳しくすることはできても、緩めることは決してできないのです。4
ライフサイクル:すべてのフックイベント
2026年7月1日時点で、リファレンスには30個のフックイベントが記載されています。1 これらは3つの周期に分けられます。セッションにつき1回のもの(SessionStart、SessionEnd)、ターンにつき1回のもの(UserPromptSubmit、Stop、StopFailure)、そしてエージェントループ内のツール呼び出しのたびに発火するもの(PreToolUse、PostToolUse)です。残りは特定の条件で発火します。設定変更、コンテキストの圧縮、サブエージェント、MCP とのやり取りなどです。
| イベント | 発火するタイミング | 実際の用途の一例 |
|---|---|---|
SessionStart |
セッションが開始または再開する | git ブランチと未解決の issue をコンテキストとして注入する |
Setup |
--init-only、または -p モードでの --init/--maintenance |
エージェント実行前に CI で依存関係をインストールする |
UserPromptSubmit |
プロンプトを送信したとき、Claude が処理する前 | 現在の日付を追記する。機密情報を含むプロンプトを拒否する |
UserPromptExpansion |
入力したコマンドがプロンプトへ展開される | スキルやコマンドの展開を監査または拒否する |
PreToolUse |
ツール呼び出しが実行される前 | 破壊的なシェルコマンドをブロックする |
PermissionRequest |
権限ダイアログが表示される | 信頼できるコマンドを自動承認して確認を省く |
PermissionDenied |
自動モードの分類器がツール呼び出しを拒否する | retry: true を返してモデルに再試行させる |
PostToolUse |
ツール呼び出しが成功した後 | 編集したファイルをすべて自動整形する |
PostToolUseFailure |
ツール呼び出しが失敗した後 | 失敗したコマンドを記録して切り分けに使う |
PostToolBatch |
並列ツール呼び出しのまとまりの後、次のモデル呼び出しの前 | エージェントループをチェックポイント保存または停止する |
Notification |
Claude Code が通知を送る | Claude が入力を必要とするときにデスクトップ通知を出す |
MessageDisplay |
アシスタントのメッセージ本文が表示されている間 | 画面上で伏せ字にする(表示のみ。記録は変わらない) |
SubagentStart |
サブエージェントが起動される | エージェントの種類ごとのコンテキストを注入する |
SubagentStop |
サブエージェントが終了する | 戻り値が返る前にサブエージェントの出力を検証する |
TaskCreated |
TaskCreate でタスクが作成される |
タスクの命名規則や範囲のルールを強制する |
TaskCompleted |
タスクが完了と記録される | 完了が確定する前に受け入れ基準を検証する |
Stop |
Claude が応答を終える | 完了ゲート。テストが通るまで終了をブロックする |
StopFailure |
API エラーによってターンが終わる | rate_limit や billing_error を通知する(記録のみ。出力は無視される) |
TeammateIdle |
エージェントチームのメンバーがアイドル状態に入ろうとする | キューを消化させてメンバーを働かせ続ける |
InstructionsLoaded |
CLAUDE.md または .claude/rules/*.md ファイルがコンテキストに読み込まれる |
どの指示がセッションに入ったかを記録する |
ConfigChange |
セッション中に設定ファイルが変更される | 許可されていない設定の編集をブロックする |
CwdChanged |
作業ディレクトリが変わる | direnv 方式の環境を再読み込みする |
FileChanged |
監視対象のファイルがディスク上で変更される | .env が変わったときに環境変数を再読み込みする |
WorktreeCreate |
--worktree または isolation: "worktree" でワークツリーが作成される |
既定の git ワークツリー準備処理を差し替える |
WorktreeRemove |
ワークツリーが削除される | セッションやサブエージェントの終了時に独自の後始末を行う |
PreCompact |
コンテキスト圧縮の前 | 失っては困る状態を保存する |
PostCompact |
圧縮が完了した後 | 重要なコンテキストを再注入する |
Elicitation |
MCP サーバーがユーザー入力を要求する | ヘッドレス実行でフォームを自動入力する |
ElicitationResult |
MCP の入力要求に回答した後 | 応答が返る前に検証または上書きする |
SessionEnd |
セッションが終了する | ログを保管し、リソースを解放する |
これらの大半は使うことはないでしょう。実運用の構成のほぼすべては、次の5つから組み立てられます。PreToolUse、PostToolUse、UserPromptSubmit、SessionStart、Stop です。残りは、必要になる日のために用意されています。
取り決め:JSON を入力し、終了コードまたは JSON を出力する
コマンド型のフックは stdin から JSON を受け取り、終了コード、stdout、stderr を通じて応答します。(HTTP 型のフックは同じ JSON を POST のボディとして受け取り、レスポンスボディを通じて応答します。)2
どのイベントも共通の外枠を届けます。session_id、transcript_path、cwd、hook_event_name(大半のイベントでは permission_mode も)に加えて、イベント固有のフィールドが付きます。Bash コマンドに対する PreToolUse フックは、次のような内容を受け取ります。
{
"session_id": "abc123",
"transcript_path": "/home/user/.claude/projects/.../transcript.jsonl",
"cwd": "/home/user/my-project",
"permission_mode": "default",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": { "command": "npm test" }
}
ほかのイベントでは末尾が入れ替わります。UserPromptSubmit は prompt を、SessionStart は source(startup/resume/clear/compact)を、Stop は stop_hook_active と last_assistant_message を運びます。サブエージェント内で発火したフックは、加えて agent_id と agent_type を受け取ります。2
終了コード
結果は3通りです。2
- 終了コード0 — 成功。Claude Code は stdout を解析して JSON の出力フィールドを読み取ります。大半のイベントでは stdout はデバッグログに送られるだけですが、
UserPromptSubmit、UserPromptExpansion、SessionStartでは、素の stdout が Claude の見えるコンテキストとして追加されます。 - 終了コード2 — ブロックを伴うエラー。stdout(JSON を含めて)は無視され、stderr がエラーメッセージとして Claude に返されます。「ブロック」が何を意味するかはイベントによって異なります。
- それ以外の終了コード — ブロックを伴わないエラー。記録には
<hook name> hook errorという通知が表示され、実行はそのまま続きます。
この最後の点は強調に値します。終了コード1は何もブロックしません。 ドキュメントもこの点を直接注意喚起しています。1は Unix で慣習的に失敗を表す終了コードであるにもかかわらず、Claude Code は終了コード1をブロックを伴わないエラーとして扱い、処理を続けてしまうのです。ポリシーを課すフックは必ず exit 2 としなければなりません。2
終了コード2がイベントごとに何をするか、以下にまとめます。2
| イベント | 終了コード2の効果 |
|---|---|
PreToolUse |
ツール呼び出しをブロックする |
PermissionRequest |
権限を拒否する |
UserPromptSubmit |
処理をブロックし、プロンプトを消去する |
UserPromptExpansion |
展開をブロックする |
Stop / SubagentStop |
停止を妨げ、会話が続く |
TeammateIdle |
メンバーがアイドル状態に入るのを妨げる |
TaskCreated / TaskCompleted |
作成を取り消す/完了を妨げる |
ConfigChange |
設定変更をブロックする(policy_settings を除く) |
PreCompact |
圧縮をブロックする |
PostToolBatch |
次のモデル呼び出しの前にエージェントループを停止する |
Elicitation / ElicitationResult |
入力要求を拒否する/応答を辞退へ変える |
WorktreeCreate |
ゼロ以外の終了コードはすべてワークツリー作成を中止する |
それ以外はブロックできません。PostToolUse と PostToolUseFailure は stderr を Claude に見せます(ツールはすでに実行済みです)。SessionStart、Notification、SessionEnd、CwdChanged、FileChanged、PostCompact、SubagentStart、Setup は stderr をユーザーにだけ見せます。StopFailure、InstructionsLoaded、MessageDisplay、PermissionDenied は終了コードを無視します。PermissionDenied で使える唯一の手立ては JSON の retry: true です。2
JSON による出力
ブロックか黙認かよりも細やかに制御したい場合は、終了コード0で終え、JSON オブジェクトを stdout に出力します。最初に一つ原則を。終了コードか JSON か、どちらか一方であって両方ではありません。 JSON は終了コード0のときにしか処理されず、終了コード2はそれを破棄します。5
共通フィールドはどのイベントでも機能します。continue: false は Claude を完全に停止させ(stopReason がユーザーに表示されます)、suppressOutput は stdout を記録から隠し、systemMessage はユーザーに警告を表示し、terminalSequence は許可リストに載ったターミナルのエスケープシーケンス(デスクトップ通知、ウィンドウタイトル、ベル)を送出します。判断用のフィールドはイベントごとに異なります。5
| イベント | 判断のパターン | 主なフィールド |
|---|---|---|
UserPromptSubmit、UserPromptExpansion、PostToolUse、PostToolUseFailure、PostToolBatch、Stop、SubagentStop、ConfigChange、PreCompact |
トップレベルの decision |
decision: "block" + reason(Claude に表示される)。許可する場合は decision を省く |
PreToolUse |
hookSpecificOutput |
permissionDecision:"allow" | "deny" | "ask" | "defer"。加えて permissionDecisionReason、および実行前にツール引数を書き換える updatedInput |
PermissionRequest |
hookSpecificOutput |
decision.behavior:"allow" | "deny"。任意で decision.updatedInput |
PermissionDenied |
hookSpecificOutput |
retry: true はモデルに再試行してよいと伝える |
PostToolUse |
hookSpecificOutput |
updatedToolOutput はツールの結果を置き換える |
Stop / SubagentStop |
hookSpecificOutput |
additionalContext:フックエラーとして数えられずに会話を続ける、エラー扱いされないフィードバック |
SessionStart、Setup、SubagentStart |
コンテキストのみ | additionalContext。加えて SessionStart 専用の initialUserMessage、sessionTitle、watchPaths、reloadSkills。ブロックは不可 |
MessageDisplay |
hookSpecificOutput |
displayContent は画面上のテキストだけを置き換える |
Elicitation / ElicitationResult |
hookSpecificOutput |
action:"accept" | "decline" | "cancel"。加えて content |
WorktreeRemove、Notification、SessionEnd、PostCompact、InstructionsLoaded、StopFailure、CwdChanged、FileChanged |
なし | 副作用のみ |
人がよくつまずく点が2つあります。1つ目。PreToolUse はトップレベルの decision パターンの例外です。かつてはトップレベルの decision/reason を使っていましたが、このイベントでは非推奨となりました("approve"/"block" は "allow"/"deny" に対応します)。hookSpecificOutput.permissionDecision を使ってください。5 2つ目。複数の PreToolUse フックの判断が食い違った場合、優先順位は deny > defer > ask > allow です。5
設定:settings.json、マッチャー、スコープ
フックの設定は3段階の入れ子になっています。イベントを選び、発火のタイミングを絞り込むマッチャーグループを加え、実行するフックハンドラーを1つ以上定義します。6
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "/path/to/lint-check.sh" }
]
}
]
}
}
これをどこに置くかでスコープが決まります。~/.claude/settings.json はあなたのすべてのプロジェクトに適用され、.claude/settings.json はプロジェクト単位でコミット可能、.claude/settings.local.json はプロジェクト単位で gitignore の対象です。そして設定の標準的な優先順位が適用されます。管理ポリシーがローカルより、ローカルがプロジェクトより、プロジェクトがユーザーより優先されます。9 フックはプラグイン(hooks/hooks.json)や、スキル・エージェントのフロントマターに同梱することもできます。また、企業の管理者はユーザーが上書きできない管理フックを強制できます。6
マッチャーはその文字列によって評価されます。"*"、""、またはマッチャーの省略はすべてに一致します。英字・数字・_・-・空白・カンマ・| だけを含む値は、完全一致の文字列またはリストです(Bash、Edit|Write)。それ以外はアンカーなしの JavaScript 正規表現になるため、Edit.* は Edit と NotebookEdit の両方に一致します。ちょうど1つのツールだけを指したいときは ^Edit$ のようにアンカーを付けてください。マッチャーは大文字・小文字を区別し、各イベントはそれぞれ固有のフィールドで照合します。ツール系イベントはツール名、SessionStart は source、SubagentStart はエージェントの種類、Notification は通知の種類です。6 ツール系イベントをより鋭く絞り込みたい場合、ハンドラーごとの if フィールドは "Bash(git *)" のような権限ルールを1つ受け付けます。ただしこれはベストエフォートで(解析できないコマンドに対しては通してしまいます)、確実な保証が必要なら if ではなく権限ルールを使ってください。6
ハンドラーには5種類あります。command(シェル)、http(POST エンドポイント)、mcp_tool、prompt(単一ターンのモデル評価)、そして agent(Read/Grep/Glob にアクセスできるサブエージェント。実験的)です。既定のタイムアウトは、command/http/mcp_tool が600秒(UserPromptSubmit では30秒、MessageDisplay では10秒に下がります)、prompt が30秒、agent が60秒です。フックごとに timeout で上書きできます。6 一致したフックはすべて並列で実行され、同一のハンドラーは重複が除かれます。また $CLAUDE_PROJECT_DIR はスクリプトからプロジェクトのルートを指し示します。
確認には /hooks を使います。これは読み取り専用のビューアで、すべてのイベント、設定されたフック、そしてそれぞれがどの設定ファイル由来かを表示します。何かを変更するには JSON を編集します(あるいは Claude に頼みましょう)。すべてを一時的に無効化するには "disableAllHooks": true を設定します。6
5つのパターン
一般化した、最小限の形です。フックのチュートリアルでは、このうちいくつかをより充実した実運用版として組み立てています。またApple 開発のためのフックでは、これらを iOS のツールチェーンに応用しています。
1. 編集後に自動整形する(PostToolUse)
公式ガイドそのままです。Claude が触れたファイルは、例外なくすべて整形されます。3
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write" }
]
}
]
}
}
コマンドは、使用しているスタックに応じて ruff format、gofmt、swiftformat などに置き換えてください。
2. 危険なコマンドをブロックする(PreToolUse、終了コード2)
#!/bin/bash
# .claude/hooks/guard-bash.sh — register on PreToolUse, matcher "Bash"
command=$(jq -r '.tool_input.command // empty')
case "$command" in
*"rm -rf"* | *"git push --force"* | *"DROP TABLE"*)
echo "Blocked: matches a destructive pattern. Propose a safer alternative." >&2
exit 2 ;;
esac
exit 0
終了コード2は呼び出しをブロックし、stderr を Claude に返します。Claude はやみくもに再試行するのではなく、方針を修正します。これに相当する JSON 版、すなわち理由を添えた permissionDecision: "deny" も同じことをしますが、"ask"(人間に判断を委ねる)や updatedInput(コマンドを書き換える)へ発展させる余地があります。5
3. セッション開始時にコンテキストを注入する(SessionStart)
SessionStart フックからの素の stdout は、Claude が見られるコンテキストになります。JSON は不要です。1
#!/bin/bash
# .claude/hooks/session-context.sh — register on SessionStart
echo "Current branch: $(git branch --show-current)"
echo "Recent commits:"
git log --oneline -5
echo "Uncommitted files: $(git status --porcelain | wc -l | tr -d ' ')"
exit 0
これは動的な状態のために使いましょう。静的な取り決めは CLAUDE.md に置くべきです。ドキュメント自身も、スクリプトを要しないコンテキストには CLAUDE.md を推奨しています。1
4. Stop に完了ゲートを設ける
Stop は Claude が応答を終えたときに発火します。これをブロックすれば、条件が満たされるまでエージェントに作業を続けさせられます。
#!/bin/bash
# .claude/hooks/stop-gate.sh — register on Stop
input=$(cat)
if [ "$(echo "$input" | jq -r '.stop_hook_active')" = "true" ]; then
exit 0 # already continuing because of this hook; don't loop forever
fi
if ! npm test --silent > /tmp/stop-gate.log 2>&1; then
jq -n '{decision: "block", reason: "Tests are failing. Fix them before finishing. Log: /tmp/stop-gate.log"}'
fi
exit 0
stop_hook_active の確認は重要です。Claude Code は Stop フックを連続8回のブロックで打ち切る上限を設けており、自分がすでに継続を引き起こしたかどうかを確認しないゲートは、その上限をあっという間に使い果たしてしまいます。7 より柔らかく誘導したい場合は、decision: "block" の代わりに hookSpecificOutput.additionalContext を返します。継続する点は同じですが、フックエラーではなく、ラベル付きのフィードバックとして扱われます。そして一度きりの条件には、組み込みの /goal コマンドが使えます。これは設定不要で、セッション単位・プロンプトベースの Stop フックとして働きます。1
5. ディスパッチャー:入り口は1つ、フックは小さくたくさん
10個のフックを登録するということは、settings.json に10個のエントリを書くということであり、それらはマシンやプロジェクトの間でばらついていきます。代わりの方法は、イベントごとにディスパッチャーを1つ登録し、規約によって振り分けることです。
#!/bin/bash
# .claude/hooks/dispatch.sh — register once per event you care about
input=$(cat)
event=$(echo "$input" | jq -r '.hook_event_name')
dir="$CLAUDE_PROJECT_DIR/.claude/hooks/$event"
[ -d "$dir" ] || exit 0
for hook in "$dir"/*.sh; do
[ -x "$hook" ] || continue
echo "$input" | "$hook" || exit $?
done
exit 0
ガードを追加する作業は、.claude/hooks/PreToolUse/ に新しいファイルを置いて chmod +x するだけになります。settings.json は一切変わらず、各スクリプトは単体でテストできるほど小さいまま保たれ、最初の終了コード2がそのまま伝わります。一つ注意点があります。ディスパッチャーは Claude Code が本来並列で実行するものを直列化してしまうため、終了コードで応答するフックに最も向いています。JSON を出力するフックは単独で使うべきです。stdout にはちょうど1つの JSON オブジェクトだけが含まれていなければならないからです。5
フック、CLAUDE.md、スキル、メモリの使い分け
4つの仕組みには、それぞれ4つの役割があります。
| 仕組み | 役割 | 選ぶ基準 |
|---|---|---|
| フック | 強制 | 飛ばすことが不可能でなければならないなら(整形、安全性、ゲートなど)フックです |
| CLAUDE.md | 案内 | モデルが毎回のセッションで知っておくべき取り決めなら(スタック、スタイル、コマンドなど)CLAUDE.mdです |
| スキル | 能力 | 独自の指示とスクリプトを持ち、関連する場面で呼び出される手順なら、スキルです |
| メモリ | 記憶 | あるセッションで得た事実で、今後のセッションが必要とするものなら、メモリです |
失敗は両方向に起こり得ます。取り決めをフックとして書き込めば、一文の案内で十分こと足りることを、壊れやすいスクリプトで強制する羽目になります。ポリシーを CLAUDE.md の文章として書けば、よりによって肝心な日に main へ強制プッシュするエージェントを抱えることになります。判断基準はこうです。モデルがこれを一度無視したら、その代償はどれほどか? 苛立ち程度なら CLAUDE.md、重大な事故ならフックです。
フックにできないこと
包み隠さぬ制約です。いずれも公式ドキュメントによります。7
- フックはツールやスラッシュコマンドを呼び出せません。 コマンド型のフックが扱えるのは stdout、stderr、終了コードだけで、それ以上はありません。
additionalContextを通じて返されるコンテキストは、プレーンテキストとして注入されます。 PostToolUseは取り消しができません。 ツールはすでに実行済みです。防止策はPreToolUseにあります。Stopはすべての応答終了時に発火します。 「タスク完了」時だけではなく、またユーザーによる中断では発火しません(API エラーの場合は代わりにStopFailureが発火します)。ゲートのロジックは、タスク途中での停止にも耐えられるようにしなければなりません。PermissionRequestはヘッドレス(-p)モードでは発火しません。 自動的な権限判断にはPreToolUseを使ってください。PreToolUseは@で参照されたファイルを見られません。 プロンプト内で@を使って取り込まれたファイルはツール呼び出しを伴わないため、その経路からパスを守るにはReadの拒否ルールを使ってください。1- 並列の
updatedInputは非決定的です。 複数の PreToolUse フックが同じツールの引数を書き換える場合、最後に終わったものが勝ちます。書き換えは1つのフックに担当させましょう。 - タイムアウトはフックを打ち切ります。 コマンド型フックの既定は600秒です(
UserPromptSubmitは30秒、MessageDisplayは10秒)。タイムアウトする遅いゲートは、実行されなかったゲートと同じです。 - 出力は10,000文字が上限です。 あふれた分はファイルに書き出され、プレビューに置き換えられます。
- フックはあなたのユーザー権限をすべて持って実行されます。 リファレンス自身がこう警告しています。フックは「あなたのユーザーアカウントがアクセスできるあらゆるファイルを変更・削除・アクセスできます。設定に追加する前に、すべてのフックコマンドを見直し、テストしてください」。8 変数はクォートで囲み、絶対パスを使い、機密性の高いファイルは避けましょう。
- 壊れたフックは、直すまですべてのセッションを損ないます。 デバッグには記録ビュー(
Ctrl+O)、claude --debug-file /tmp/claude.log、あるいはセッション途中の/debugを使います。よくある落とし穴は、起動時に文字を出力するシェルプロファイルが、フックの JSON 出力を壊してしまうことです。7
よくある質問
Claude Code のフックとは何ですか?
フックとは、ユーザーが定義するコマンド(シェルスクリプト、HTTP エンドポイント、MCP ツール、またはモデルへのプロンプト)であり、Claude Code がライフサイクルの特定の地点で自動的に実行するものです。3 フックは stdin からイベントの JSON を受け取り、終了コードまたは JSON で応答します。ツール呼び出しをブロックする、コンテキストを注入する、引数を書き換える、エージェントに作業を続けさせる、といった具合です。CLAUDE.md の指示とは違い、フックはモデルの振る舞いにかかわらず毎回実行されます。
PreToolUse フックと権限の違いは何ですか?
権限ルールは宣言的です。Claude Code 自身が評価する、静的な許可・拒否・確認のパターンです。PreToolUse フックはプログラム可能です。あなたのコードがツール入力の全体を調べて判断します。フックは権限モードの検査より前に発火するため、フックの "deny" は bypassPermissions モードでも有効です。ただしフックの "allow" は、設定ファイルの拒否ルールを覆せません。4 パターンで表現できることには権限ルールを使い、判断にロジックや外部の状態、入力の書き換えが必要なときにフックを持ち出しましょう。
フックはヘッドレス(-p)モードでも動きますか?
動きます。ただしドキュメントに記された例外が1つあります。PermissionRequest フックは非対話モードでは発火しないため、自動的な権限判断は PreToolUse に置くべきです。7 さらにヘッドレスモードでは、対話セッションでは無視される選択肢が一つ使えるようになります。permissionDecision: "defer" です。これはツール呼び出しを一時停止し、外側で包むプロセス(Agent SDK アプリ、独自の UI など)が入力を集めて、後からセッションを再開できるようにします。5
フックは動くのに、何もブロックしないのはなぜですか?
ほとんどの場合、取り決めに違反しています。終了コード1はブロックしません。ブロックするのは終了コード2だけで、しかもブロックに対応したイベントに限られます。2 JSON による判断は終了コード0のときにしか解析されません。{"decision": "block"} を出力してから終了コード2で終えるスクリプトは、その JSON が破棄されてしまいます。そしてマッチャーは大文字・小文字を区別します。bash は Bash に決して一致しません。まず /hooks で登録を確認し、次にサンプルの JSON をスクリプトにパイプで渡して echo $? を調べてテストしましょう。7
出典
2026年7月1日に公式ドキュメントと照合して検証しました。フックの API は Claude Code v2.1.x のリリースを通じて大きく変わっています(新しいイベント、新しいフィールド、マッチャーの意味づけ)。そのため、バージョンに左右される詳細は「この日付時点のもの」として扱ってください。
このサイトの関連記事: プロンプトフックやエージェントフックまで含めたシステム全体像はClaude Code ガイドのフックの節を、完全な設定を伴う5つの実運用ビルドはフックのチュートリアルを、iOS への応用パターンはApple 開発のためのフックを、そして Claude Code をまだインストールしていない方はクイックスタートをご覧ください。
-
Anthropic, “Hooks reference — Hook lifecycle and hook events.” code.claude.com/docs/en/hooks#hook-events ↩↩↩↩↩↩
-
Anthropic, “Hooks reference — Hook input and output; exit code output; exit code 2 behavior per event.” code.claude.com/docs/en/hooks#exit-code-output ↩↩↩↩↩↩↩↩
-
Anthropic, “Automate actions with hooks.” code.claude.com/docs/en/hooks-guide ↩↩↩
-
Anthropic, “Hooks guide — Hooks and permission modes.” code.claude.com/docs/en/hooks-guide#hooks-and-permission-modes ↩↩
-
Anthropic, “Hooks reference — JSON output and decision control.” code.claude.com/docs/en/hooks#json-output ↩↩↩↩↩↩↩
-
Anthropic, “Hooks reference — Configuration: hook locations, matcher patterns, hook handler fields, the /hooks menu.” code.claude.com/docs/en/hooks#configuration ↩↩↩↩↩↩
-
Anthropic, “Hooks guide — Limitations and troubleshooting.” code.claude.com/docs/en/hooks-guide#limitations-and-troubleshooting ↩↩↩↩↩
-
Anthropic, “Hooks reference — Security considerations.” code.claude.com/docs/en/hooks#security-considerations ↩
-
Anthropic, “Claude Code settings.” code.claude.com/docs/en/settings ↩