Claude Code のフック徹底解説 — エージェントを囲む決定論的なレイヤー
Claude Code のフックとは何でしょうか。 フックは、Claude Code がライフサイクルの決まったポイント(ツール呼び出しの前、編集の後、セッション開始時、Claude が応答を終えたとき)で自動的に実行するユーザー定義のシェルコマンドです(HTTP エンドポイント、MCP ツール、モデルへのプロンプトも含みます)。1 CLAUDE.md がモデルに「おそらく従うであろう」指示を与えるのに対し、フックはモデルが協力するかどうかに関係なく実行されます。セッション中に /hooks と入力すれば、すべてのライフサイクルイベントと、それぞれに何が紐づいているかを確認できます。
多くの開発者は、2つの制御レイヤーで Claude Code を動かしています。エージェントに何を許すかを制限するパーミッションと、何をすべきかを記述する CLAUDE.md です。フックは3つ目のレイヤーであり、何かを保証できる唯一のレイヤーでもあります。以下では、メンタルモデル、現行ドキュメントに載っているすべてのライフサイクルイベント、正確な入出力の契約、設定方法、動作する5つのパターン、そして判断のフレームワークを扱います。API の詳細はすべて 2026年8月8日時点の公式フックリファレンスとガイドで検証しました。この仕組みは変化が速いため、本記事とリファレンスが食い違う場合はリファレンスが正です。(Claude Code は初めてですか。まずは 5分でできるセットアップ、または Claude Code 入門ルートからどうぞ。)
TL;DR: フックは stdin で JSON を受け取り、終了コードまたは stdout の JSON で応答します。exit 0 は許可、exit 2 はブロック(ブロックできるイベントの場合)、そして Unix で慣例的に失敗を表す exit 1 は何もブロックしません。これがフック最大の落とし穴です。2 設定は settings.json の PreToolUse や Stop といったイベント名の下に置き、matcher で絞り込みます。必ず起きなければならないことにはフックを、モデルが知ってさえいればよいことには CLAUDE.md を使いましょう。
メンタルモデル: 非決定論的な中核を囲む保証
コーディングエージェントは確率的なシステムです。編集のたびに Prettier を実行するよう頼めば、たいていは実行してくれます。しかし変更が些細に見えるとき、コンテキストが長くなったとき、あるいは言い回しの受け取られ方が変わったときには、その手順を飛ばすかもしれません。CLAUDE.md もスキルもプロンプトも、すべて提案にすぎません。品質は高く、たいていは従われますが、保証はされないのです。
フックは、その中核を囲む決定論的なシェルです。ガイドは「フックはユーザー定義のシェルコマンドです」という一行の定義から始まり、要点をはっきりこう述べています。フックがもたらすのは「決定論的な制御、つまり LLM が実行を選ぶことに頼るのではなく、特定の動作が常に起こること」だ、と。3(ガイドのこの一行は現在の実態を控えめに言い過ぎています。リファレンスのより詳しい定義にはすでに HTTP エンドポイントと LLM プロンプトが加わっており、ハンドラーは MCP ツールとしても提供されます。後述の「設定」で扱います。)フォーマッターは編集のたびに動きます。コマンドガードはすべての Bash 呼び出しを評価します。完了ゲートは終了のたびにチェックします。
この強制力は見せかけではなく本物です。PreToolUse フックはパーミッションモードの判定より前に発火するため、permissionDecision: "deny" を返すフックは bypassPermissions モードでも --dangerously-skip-permissions の下でもツールをブロックします。逆は成り立ちません。"allow" を返すフックが設定の deny ルールを緩めることはできないのです。フックはパーミッションが許す範囲よりも厳しくはできますが、緩めることはできません。4
ライフサイクル: すべてのフックイベント
2026年8月8日時点で、リファレンスには31個のフックイベントが記載されています。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 方式の環境を再読み込みする |
DirectoryAdded |
/add-dir または SDK の register_repo_root により、セッション中に作業ディレクトリが登録されたとき(v2.1.219 以降) |
そのリポジトリのコンテキストを、参加した瞬間に読み込む |
FileChanged |
監視対象のファイルがディスク上で変更されたとき | .env の変更時に環境変数を再読み込みする |
WorktreeCreate |
--worktree または isolation: "worktree" で worktree が作成されたとき |
既定の git worktree 作成処理を置き換える |
WorktreeRemove |
worktree が削除されたとき | セッションまたはサブエージェント終了時の独自クリーンアップ |
PreCompact |
コンテキストのコンパクションの前 | 失うわけにいかない状態を保存する |
PostCompact |
コンパクションの完了後 | 重要なコンテキストを再注入する |
Elicitation |
MCP サーバーがユーザー入力を要求したとき | ヘッドレス実行でフォームを自動入力する |
ElicitationResult |
MCP の elicitation に回答した後 | 応答が返る前に検証、または上書きする |
SessionEnd |
セッションが終了するとき | ログの保管、リソースの解放 |
これらの大半は必要にならないでしょう。実運用の構成はほぼすべて、PreToolUse、PostToolUse、UserPromptSubmit、SessionStart、Stop の5つで組み立てられています。残りは、必要になるその日のために存在しています。
契約: JSON を受け取り、終了コードか JSON を返す
コマンド型のフックは stdin で JSON を受け取り、終了コード、stdout、stderr で応答します。(HTTP フックは同じ JSON を POST のボディとして受け取り、レスポンスボディで応答します。)2
どのイベントも共通のエンベロープを届けます。session_id、transcript_path、cwd、hook_event_name に加え、ほとんどのイベントでは permission_mode も含まれ、さらにイベント固有のフィールドが続きます。Bash コマンドに対する PreToolUse フックが受け取るのは次のような JSON です。
{
"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/fork。5つ目の値は v2.1.214 のフォークセッションとともに追加されたもので、古い4値のリストからコピーした source マッチのフックはフォークを黙って取りこぼします)を、Stop は stop_hook_active と last_assistant_message を運びます。サブエージェント内で発火したフックは、加えて agent_id と agent_type を受け取ります。2
終了コード
結果は3通りです。2
- Exit 0 — 成功。Claude Code は stdout を JSON 出力フィールドとして解析します。ほとんどのイベントでは stdout はデバッグログにしか流れませんが、
UserPromptSubmit、UserPromptExpansion、SessionStartではプレーンな stdout が Claude の見えるコンテキストとして追加されます。 - Exit 2 — ブロックエラー。stdout(JSON を含む)は無視され、stderr がエラーメッセージとして Claude に返されます。「ブロック」が何を意味するかはイベントによって異なります。
- それ以外の終了コード — ブロックしないエラー。トランスクリプトに
<hook name> hook errorという通知が出て、処理はそのまま続行します。
最後の行は太字にする価値があります。exit 1 は何もブロックしません。 ドキュメントもこの点を直接警告しています。1 は Unix で慣例的に失敗を表すコードであるにもかかわらず、Claude Code は exit 1 をブロックしないエラーとして扱い、そのまま先へ進むのです。ポリシーを課すフックは exit 2 でなければなりません。2
イベントごとの exit 2 の効果は次のとおりです。2
| イベント | exit 2 の効果 |
|---|---|
PreToolUse |
ツール呼び出しをブロックする |
PermissionRequest |
パーミッションを拒否する |
UserPromptSubmit |
処理をブロックし、プロンプトを消去する |
UserPromptExpansion |
展開をブロックする |
Stop / SubagentStop |
停止を防ぎ、会話を継続させる |
TeammateIdle |
メンバーがアイドルになるのを防ぐ |
TaskCreated / TaskCompleted |
作成をロールバックする / 完了を防ぐ |
ConfigChange |
設定変更をブロックする(policy_settings を除く) |
PreCompact |
コンパクションをブロックする |
PostToolBatch |
次のモデル呼び出しの前にエージェントループを停止する |
Elicitation / ElicitationResult |
elicitation を拒否する / 応答を辞退に変える |
WorktreeCreate |
非ゼロ終了はいずれも worktree の作成を中止する |
それ以外はブロックできません。PostToolUse と PostToolUseFailure は stderr を Claude に見せます(ツールはすでに実行済みです)。SessionStart、Notification、SessionEnd、CwdChanged、FileChanged、PostCompact、SubagentStart、Setup は stderr をユーザーにだけ表示し、DirectoryAdded は stderr をデバッグログにのみ送ります。StopFailure、InstructionsLoaded、MessageDisplay、PermissionDenied は終了コードを無視します。PermissionDenied で唯一効くのは JSON の retry: true です。2
JSON 出力
ブロックか沈黙かより細かく制御したいときは、exit 0 で stdout に JSON オブジェクトを出力します。まず大前提を1つ。終了コードか JSON か、どちらか一方であって両方ではありません。 JSON は exit 0 のときにだけ処理され、exit 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 |
TeammateIdle、TaskCreated、TaskCompleted |
共通の continue |
continue: false と stopReason がメンバーやタスクの流れを完全に止めます(イベントごとのブロックは exit 2 です) |
WorktreeCreate |
パスを返す | コマンドフックは worktree のパスを stdout に出力し、HTTP フックは hookSpecificOutput.worktreePath を返します。失敗、またはパスが無い場合は作成が失敗します |
WorktreeRemove、Notification、SessionEnd、PostCompact、InstructionsLoaded、StopFailure、CwdChanged、DirectoryAdded、FileChanged |
なし | 副作用のみ |
つまずきやすい点が2つあります。1つ目は、PreToolUse がトップレベル decision パターンの例外だということです。歴史的にはトップレベルの decision/reason を使っていましたが、このイベントでは非推奨になりました("approve"/"block" はそれぞれ "allow"/"deny" に対応します)。hookSpecificOutput.permissionDecision を使ってください。5 2つ目は、複数の PreToolUse フックの判断が食い違ったときの優先順位で、deny > defer > ask > allow と、最も制限の強い答えが勝ちます。とはいえ、この同点処理に寄りかかるのではなく、判断ごとに担当するフックを1つに決めておきましょう。5
設定: settings.json、matcher、スコープ
フックの設定は3階層になっています。イベントを選び、発火条件を絞るmatcher グループを追加し、実行するフックハンドラーを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
matcher は文字の中身によって解釈されます。"*"、""、あるいは matcher の省略はすべてにマッチします。英字、数字、_、-、スペース、カンマ、| だけからなる値は完全一致の文字列またはリストです(Bash、Edit|Write)。それ以外はアンカーなしの JavaScript 正規表現になるため、Edit.* は Edit と NotebookEdit の両方にマッチします。ツールを1つに限定したいときは ^Edit$ とアンカーしてください。matcher は大文字と小文字を区別し、イベントごとに照合するフィールドも異なります。ツールイベントではツール名、SessionStart では source、SubagentStart ではエージェントの種類、Notification では通知の種類です。6 ツールイベントをさらに鋭く絞り込みたい場合、ハンドラーごとの if フィールドが "Bash(git *)" のようなパーミッションルールを1つ受け付けます。ただしこれはベストエフォート(解析できないコマンドではフェイルオープンします)なので、確実な保証が必要なら if ではなくパーミッションルールを使ってください。6 知っておくべき仕様変更が1つあります。v2.1.214 以降、if の中の単一セグメントのパスパターン(Edit(src/**) など)は作業ディレクトリ直下の src にしかマッチしません。そのリリース以前に書かれた if は、packages/app/src/ のようなネストしたパスに黙ってマッチしなくなります。以前の「どの深さでも」という挙動が欲しければ Edit(**/src/**) と書いてください。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、exit 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
exit 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回までに制限しており(CLAUDE_CODE_STOP_HOOK_BLOCK_CAP で引き上げ可能)、自分がすでに継続を発動させたかどうかを確認しないゲートは、その回数を一気に使い切ってしまいます。7 もっと穏やかに誘導したいときは、decision: "block" ではなく hookSpecificOutput.additionalContext を返します。継続する点は同じですが、フックエラーではなくラベル付きのフィードバックとして扱われます。単発の条件であれば、組み込みの /goal コマンドが、設定不要でセッション限定のプロンプトベース Stop フックとして働きます。1
5. ディスパッチャー: 1つの入口と、多数の小さなフック
10個のフックを登録するということは、マシンやプロジェクトの間でずれていく10個の settings.json エントリを抱えるということです。代わりに、イベントごとにディスパッチャーを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 は変わらず、各スクリプトは単体でテストできる小ささを保ち、最初の exit 2 がそのまま伝播します。注意点が1つ。ディスパッチャーは Claude Code なら並列に実行するものを直列化しますし、終了コード方式のフックに最も向いています。stdout にはちょうど1つの JSON オブジェクトしか置けないため、JSON を出力するフックは単独で登録したままにしてください。5
フック、CLAUDE.md、スキル、メモリーの使い分け
4つの仕組みに、4つの役割があります。
| 仕組み | 役割 | 選び方の基準 |
|---|---|---|
| フック | 強制 | 飛ばされることがあってはならないなら(フォーマット、安全性、ゲート)フックです |
| CLAUDE.md | 指針 | 毎セッションでモデルに知っておいてほしい取り決めなら(スタック、スタイル、コマンド)CLAUDE.md です |
| スキル | 能力 | 独自の手順書とスクリプトを持ち、関連する場面で呼び出される手続きならスキルです |
| メモリー | 記憶 | あるセッションで学んだ事実を、将来のセッションでも必要とするならメモリーです |
失敗は両方向に起こります。取り決めをフックとして書けば、一文の指針で足りることを強制する壊れやすいスクリプトを抱え込みます。ポリシーを CLAUDE.md の散文として書けば、よりによって大事な日に main へ force push するエージェントを抱え込みます。判断の物差しはこうです。モデルがこれを一度無視したとき、その代償は何か。面倒で済むなら CLAUDE.md、インシデントになるならフックです。
フックにできないこと
公式ドキュメントに基づく、正直な限界です。7
- フックはツールやスラッシュコマンドを呼び出せません。 コマンドフックが話せるのは stdout、stderr、終了コードだけです。
additionalContextで返したコンテキストはプレーンテキストとして注入されます。 PostToolUseは取り消せません。 ツールはすでに実行されています。予防はPreToolUseの役目です。Stopは応答が終わるたびに発火します。 「タスク完了」のときだけではありませんし、ユーザーによる中断では発火しません(API エラーでは代わりにStopFailureが発火します)。ゲートのロジックは、タスク途中での停止にも耐えられなければなりません。PermissionRequestは素のヘッドレス(-p)実行では発火しません。 Agent SDK のcanUseToolコールバックがプロンプトを供給する場合の-p実行と、バックグラウンドのサブエージェントによるツール呼び出しでは発火します。それ以外の自動化にはPreToolUseを使ってください。PreToolUseは@で参照されたファイルを見られません。 プロンプト内の@で取り込まれたファイルにはツール呼び出しが伴いません。その経路からパスを守るにはReadの deny ルールを使います。1- 並列実行時の
updatedInputは設計上あてになりません。 複数の PreToolUse フックが同じツールの引数を書き換えると、生き残る書き換えは1つだけで、どれが残るかは選べません。書き換えは1つのフックに任せましょう。 - タイムアウトはフックをキャンセルします。 コマンドフックの既定は600秒(
UserPromptSubmitは30秒、MessageDisplayは10秒)です。タイムアウトする遅いゲートは、実行されなかったゲートと同じです。 - 出力は10,000文字で打ち切られます。 あふれた分はファイルに書き出され、プレビューに置き換えられます。
- フックはあなたのユーザー権限をそのまま持って実行されます。 リファレンス自身の警告はこうです。フックは「ユーザーアカウントがアクセスできるあらゆるファイルを、変更、削除、参照できます。設定に追加する前に、すべてのフックコマンドを確認しテストしてください」。8 変数はクォートし、絶対パスを使い、機微なファイルには触れないようにしましょう。
- 壊れたフックは、直すまで全セッションを劣化させます。 デバッグはトランスクリプト表示(
Ctrl+O)、claude --debug-file /tmp/claude.log、あるいはセッション中の/debugで行います。典型的な落とし穴は、起動時に何かを echo するシェルのプロファイルが、フックの JSON 出力を壊してしまうケースです。7
よくある質問
Claude Code のフックとは何ですか
フックは、Claude Code がライフサイクルの特定のポイントで自動的に実行するユーザー定義のコマンド(シェルスクリプト、HTTP エンドポイント、MCP ツール、モデルへのプロンプト)です。3 stdin でイベントの JSON を受け取り、終了コードまたは JSON で応答します。ツール呼び出しをブロックする、コンテキストを注入する、引数を書き換える、エージェントに作業を続けさせる、といった具合です。CLAUDE.md の指示と違い、モデルの振る舞いに関係なく毎回実行されます。
PreToolUse フックとパーミッションの違いは何ですか
パーミッションルールは宣言的で、Claude Code 自身が評価する静的な allow/deny/ask のパターンです。PreToolUse フックはプログラマブルで、あなたのコードがツール入力の全体を調べて判断します。フックはパーミッションモードの判定より前に発火するため、フックの "deny" は bypassPermissions モードでも効きます。ただしフックの "allow" が設定の deny ルールを上書きすることはできません。4 パターンで表現できることはパーミッションルールに任せ、ロジックや外部の状態、入力の書き換えが必要な判断のときにフックへ手を伸ばしてください。
ヘッドレス(-p)モードでもフックは動きますか
動きます。ただし1点だけ注意があります。PermissionRequest フックは素の -p 実行では発火しません(そこにはパーミッションのプロンプトを供給するものがないためです)。とはいえ、Agent SDK の canUseTool コールバックが供給する場合と、バックグラウンドのサブエージェントによるツール呼び出しでは発火します。素のヘッドレス実行における自動のパーミッション判断は PreToolUse に置いてください。7 ヘッドレスモードは、対話セッションでは無視される選択肢も1つ解放します。permissionDecision: "defer" です。これはツール呼び出しを一時停止し、それを包むプロセス(Agent SDK のアプリや独自の UI)が入力を集めて、後からセッションを再開できるようにします。5
フックは動くのに何もブロックしないのはなぜですか
ほぼ必ず契約違反です。exit 1 はブロックしません。ブロックするのは exit 2 だけで、しかもブロックに対応したイベントに限られます。2 JSON の判断は exit 0 のときにしか解析されないため、{"decision": "block"} を出力してから exit 2 するスクリプトは、その JSON を捨てられてしまいます。さらに matcher は大文字と小文字を区別するので、bash が Bash にマッチすることはありません。まず /hooks で登録を確認し、次にサンプルの JSON をスクリプトにパイプして echo $? を確かめてください。7
出典
2026年8月8日時点の公式ドキュメントで検証しました。フックの API は Claude Code v2.1.x の各リリースを通じて実質的に変わってきているため(新しいイベント、新しいフィールド、matcher の意味論)、バージョンに左右される細部は「この日付時点のもの」として扱ってください。
このサイトの関連記事: プロンプトフックやエージェントフックまで含めたシステム全体像は 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 ↩