Hooks reference
概要
Claude Code のライフサイクルの特定の時点で自動実行される、ユーザー定義のシェルコマンド・HTTP エンドポイント・LLM プロンプト。ターミナル、IDE 拡張、デスクトップアプリ、Claude Code on the web のいずれでも同じ hook イベントが発火する。
仕様
実行の流れ
- イベントが発火し matcher が一致すると、イベントの JSON コンテキストが hook ハンドラに渡される。command hook では stdin、HTTP hook では POST のリクエストボディ
- イベントのカデンス
- セッションに 1 回:
SessionStart、SessionEnd
- ターンに 1 回:
UserPromptSubmit、Stop、StopFailure
- agentic loop 内のツール呼び出しごと:
PreToolUse、PostToolUse(EndConversation の呼び出しは両方をスキップする)
イベント一覧
設定の構造
JSON の settings ファイルで 3 段のネストとして定義する。
- 反応する hook イベントを選ぶ
- 発火条件を絞る matcher group を追加する
- 一致したときに動く hook handler を 1 つ以上定義する
用語: hook event(ライフサイクル上の地点)、matcher group(フィルタ)、hook handler(実行されるシェルコマンド・HTTP エンドポイント・MCP ツール・プロンプト・agent)。
定義場所
- settings ファイル、managed policy settings、plugin の hooks は subagent 内でも動く。subagent がツールを呼ぶと
PreToolUse / PostToolUse などがメイン会話と同じ hook を発火させ、入力に agent_id と agent_type が入る
- 管理者は
allowManagedHooksOnly で user / project / plugin の hook をブロックできる。managed settings の enabledPlugins で強制有効化した plugin の hook は除外される
- hook のエントリは settings のレベル間で置き換えではなくマージされる。
disableAllHooks は managed settings の外からは managed hook を無効化できない
- HTTP hook の allowlist(
allowedHttpHookUrls、httpHookAllowedEnvVars)は managed policy settings を含むすべてのソースの hook に適用される
- Claude Code on the web の cloud session はローカルの
~/.claude/settings.json を読まない
matcher パターン
- 正規表現経路では
RegExp.prototype.test で判定されるため値のどこかに一致すれば成功する。Edit.* は Edit と NotebookEdit の両方に一致する。全体一致には ^Edit$ のように anchor する
- カンマ区切りと前後空白の許容は v2.1.191 以降が必要
- 完全一致集合でのハイフンは v2.1.195 以降が必要。それ以前は
code-reviewer が anchor 無し正規表現として扱われ senior-code-reviewer にも発火する
FileChanged と StopFailure は英数字・_・| のみの狭い完全一致集合を使う。ハイフン・スペース・カンマがあると正規表現経路に残り、区切りは | のみ
イベントごとの matcher 対象:
- matcher 非対応のイベントに
matcher を書いても黙って無視される
MCP ツールへの一致
- MCP ツールは通常のツールとしてツールイベントに現れる。命名は
mcp__<server>__<tool>
- サーバーの全ツールに一致させるには
.* を付ける。mcp__memory のような形は完全一致文字だけなので文字列比較となり何にも一致しない
- plugin バンドルの MCP サーバーのツールは
mcp__plugin_<plugin-name>_<server-name>__<tool>。素のサーバーキーで書いた matcher は発火しない
hook handler の種類
- 一致したすべての hook は並列に実行される。同じ handler を複数の settings ファイルで定義しても 1 回だけ動く。plugin や skill の同じ handler は別扱い
- handler は現在のディレクトリで Claude Code の環境で動く。リモートの web 環境では
$CLAUDE_CODE_REMOTE が "true" になる。v2.1.199 以降、Remote Control 接続中は $CLAUDE_CODE_BRIDGE_SESSION_ID が設定される
共通フィールド
if は permission rule をちょうど 1 つだけ持つ。&& や || やリスト構文は無い。複数条件には handler を分ける
- ファイルツールの
if で "Edit(src/**)" のような単一セグメントのディレクトリパターンは、作業ディレクトリの src とその配下のみに一致する。任意の深さには "Edit(**/src/**)"。v2.1.214 より前は任意の深さに一致した
Bash パターンの if の挙動(先頭の VAR=value 代入は照合前に取り除かれる):
- Bash コマンドを解析できない場合もフィルタは fail open して hook を動かす。
if は best-effort なので、確実な許可・拒否には permission system を使う
command hook のフィールド
exec form と shell form:
- exec form(
args あり): command を PATH 上の実行ファイルとして解決し、args を引数ベクタとして直接起動する。シェルが無いため各 args 要素はそのまま 1 引数になり、${CLAUDE_PLUGIN_ROOT} などのパスプレースホルダはプレーン文字列として置換される。アポストロフィ・$・バッククォートはそのまま通る
- shell form(
args 無し): command 文字列がシェル(macOS / Linux は sh -c、Windows は Git Bash、Git Bash が無ければ PowerShell)に渡される。トークン化・変数展開・パイプ・&&・リダイレクト・glob が解釈される
- パスプレースホルダを参照する hook では exec form を推奨する。shell form では各プレースホルダをダブルクォートで囲む
- Windows の exec form では
command が .exe のような実際の実行ファイルに解決される必要がある。node_modules/.bin の .cmd / .bat shim はシェル無しでは起動できない
- どちらの form も
CLAUDE_PROJECT_DIR / CLAUDE_PLUGIN_ROOT / CLAUDE_PLUGIN_DATA を起動プロセスの環境変数としてエクスポートする
- plugin hook は exec form でのみ
${user_config.*} の値を置換する。shell form で参照するとエラーになる。代わりに $CLAUDE_PLUGIN_OPTION_<KEY> 環境変数を読むか args を設定して exec form にする
HTTP hook のフィールド
- JSON 入力は
Content-Type: application/json の POST ボディとして送られる
- ツールのテキストコンテンツは command hook の stdout として扱われる。JSON 出力として妥当なら決定として処理され、そうでなければプレーンテキストとして表示される
- サーバーが未接続、またはツールが
isError: true を返すと、非ブロッキングエラーになり実行は続く
SessionStart と Setup は通常サーバー接続完了より前に発火するため、初回は「未接続」エラーになりうる
prompt / agent hook のフィールド
パスプレースホルダ
${CLAUDE_PROJECT_DIR}: プロジェクトルート。stdio MCP サーバーと plugin の LSP サーバーの環境にも設定される
${CLAUDE_PLUGIN_ROOT}: plugin のインストールディレクトリ。plugin 更新ごとに変わる
${CLAUDE_PLUGIN_DATA}: plugin の永続データディレクトリ
skill / agent の frontmatter で定義する hook
- すべての hook イベントをサポートする。subagent では
Stop hook が自動的に SubagentStop に変換される
- 設定形式は settings ベースの hook と同じで、コンポーネントの生存期間にスコープされ終了時に片付けられる
- project subagent の frontmatter hook は、agent ファイルのあるフォルダの workspace trust ダイアログを承認した後にのみ動く(v2.1.218 以降)
/hooks メニュー
- 設定済み hook の読み取り専用ブラウザ。イベントごとの hook 数、matcher、各 handler の詳細を表示する
- 5 種類すべての hook タイプを表示し、
[type] 接頭辞と出所(User Settings / Project Settings / Local Settings / Plugin Hooks / Session Hooks / Built-in Hooks)が付く
- 読み取り専用。追加・変更・削除は settings JSON を直接編集する
無効化・削除
- 削除は settings JSON からエントリを消す
- 一時的にすべて無効にするには
"disableAllHooks": true。個別の hook だけを無効化する方法は無い
- managed policy settings で設定された hook は、user / project / local の
disableAllHooks では無効化できない。managed settings レベルの disableAllHooks のみが可能
- settings ファイルの hook の直接編集は通常ファイル監視で自動的に反映される
共通の入力フィールド
model フィールドを受け取れるのは SessionStart hook だけで、存在は保証されない。$CLAUDE_MODEL 環境変数は無い
- Claude Code は起動するすべてのサブプロセス(hook を含む)から
OTEL_* の exporter 変数を取り除く
終了コードによる出力
- Exit 0: 成功。stdout を JSON 出力フィールドとして解析する。JSON 出力は exit 0 のときのみ処理される。多くのイベントで stdout はデバッグログに書かれトランスクリプトには出ない。例外は
UserPromptSubmit、UserPromptExpansion、SessionStart で、stdout が Claude の見るコンテキストとして追加される
- exit 0 の hook の stderr はデバッグログのみに行き、Claude は見ない
- Exit 2: ブロッキングエラー。stdout とその中の JSON は無視され、stderr のテキストがエラーメッセージとして Claude に返される
- exit 2 で JSON 出力のスキーマ検証に失敗する JSON を出した場合もブロックする。stderr をブロック理由として使い、検証失敗はデバッグログに記録される(v2.1.214 以降)
- それ以外の終了コード: 多くのイベントで非ブロッキングエラー。処理は続き、トランスクリプトに
<hook name> hook error と stderr の先頭行が Failed with non-blocking status code: 付きで表示される
- exit 1 は非ブロッキングエラーとして扱われる。ポリシーを強制するなら
exit 2 を使う。例外は WorktreeCreate で、非ゼロ終了コードはすべて worktree 作成を中断する
exit 2 のイベント別の挙動
HTTP レスポンスの扱い
- 2xx + 空ボディ: 成功(exit 0 で出力なしと等価)
- 2xx + プレーンテキスト: 成功。テキストがコンテキストとして追加される
- 2xx + JSON: 成功。command hook と同じ JSON 出力スキーマで解析される
- 非 2xx: 非ブロッキングエラー。実行は続く
- 接続失敗・タイムアウト: 非ブロッキングエラー。実行は続く
- HTTP hook はステータスコードだけではブロッキングエラーを表せない。ブロックには 2xx + 適切な決定フィールドを含む JSON ボディを返す
JSON 出力
- 終了コードと JSON はどちらか一方を使う。JSON は exit 0 のときのみ処理され、exit 2 では無視される
- stdout には JSON オブジェクトだけを出す
additionalContext、systemMessage、プレーンな stdout を含む hook の出力文字列は 10,000 文字が上限。超えるとファイルに保存され、プレビューとファイルパスに置き換えられる
- hook は制御端末を持たずに動くため
/dev/tty へのエスケープシーケンス書き込みは失敗する。代わりに terminalSequence を返す。tmux / GNU screen 内でも、/dev/tty の無い Windows でも動く。v2.1.141 以降が必要
- 許可されるのは OSC
0/1/2(ウィンドウ・アイコンタイトル)、OSC 9(iTerm2 / ConEmu / Windows Terminal / WezTerm の通知、9;4 のタスクバー進捗を含む)、OSC 99(Kitty の通知)、OSC 777(urxvt / Ghostty / Warp の通知)、素の BEL。終端は BEL か ST
additionalContext
hookSpecificOutput の中に hookEventName とともに返す
- Claude Code は文字列を system reminder で包み、hook が発火した位置で会話に挿入する。Claude は次のモデルリクエストで読むが、UI 上のチャットメッセージとしては現れない
- 挿入位置
SessionStart / Setup / SubagentStart: 会話の先頭、最初のプロンプトより前
UserPromptSubmit / UserPromptExpansion: 送信されたプロンプトと並んで
PreToolUse / PostToolUse / PostToolUseFailure / PostToolBatch: ツール結果の隣
Stop / SubagentStop: ターンの末尾。会話は続き Claude はフィードバックに反応できる
- 同じイベントで複数の hook が返した場合、Claude はすべての値を受け取る。1 つの値が 10,000 文字を超えると、全文がセッションディレクトリのファイルに書かれ、パスと短いプレビューが渡される
- 命令的なシステム指示ではなく事実の記述として書く。out-of-band のシステムコマンドのように書かれたテキストは prompt-injection 防御を起こし、Claude がコンテキストとして扱わずユーザーに提示することがある
- 注入されたテキストはセッショントランスクリプトに保存される。
--continue / --resume では過去ターンの hook を再実行せず保存済みテキストを再生するため、タイムスタンプやコミット SHA のような値は古くなる。SessionStart hook は resume 時に source を "resume"(--fork-session なら "fork")として再実行される
decision control
内容の書き換えができるイベント:
-
PreToolUse: hookSpecificOutput 直下の updatedInput がツールの引数を実行前に置き換える
-
PermissionRequest: decision オブジェクト内の updatedInput
-
PostToolUse: updatedToolOutput がツールの結果を置き換える
-
UserPromptSubmit: プロンプトは置き換えられず、additionalContext を並べて注入するだけ
-
トップレベル decision の唯一の値は "block"。許可するには decision を省くか、JSON 無しで exit 0 する
- 複数の
PreToolUse hook が異なる決定を返した場合の優先順位は deny > defer > ask > allow
- hook が
"ask" を返すと、permission prompt に出所のラベル([User] / [Project] / [Plugin] / [Local])が付く
- hook の
"ask" は auto mode でも permission prompt を強制する。classifier は拒否できるが黙って承認することはできない(v2.1.211 以降)
AskUserQuestion と ExitPlanMode は user interaction を要し、-p の非対話モードでは通常ブロックされる。permissionDecision: "allow" と updatedInput を併せて返すとその要件を満たす。"allow" だけでは不十分
- v2.1.199 以降、
_meta["anthropic/requiresUserInteraction"] が付いた MCP ツールは、updatedInput の有無に関わらず hook の "allow" で承認プロンプトを飛ばせない
PreToolUse の旧来のトップレベル decision / reason は非推奨。"approve" は "allow"、"block" は "deny" に対応する
"defer":
claude -p をサブプロセスとして実行しその JSON 出力を読む統合(Agent SDK アプリやカスタム UI)向け。-p の非対話モードでのみ尊重され、対話セッションでは警告を記録して無視される
- 流れ: hook が
"defer" を返す → ツールは実行されず stop_reason: "tool_deferred" で終了し保留中のツール呼び出しがトランスクリプトに保持される → 呼び出し元が deferred_tool_use を読み自前 UI で回答を集める → claude -p --resume <session-id> で同じツール呼び出しが再び PreToolUse を発火 → hook が updatedInput に回答を入れて "allow" を返す
deferred_tool_use はツールの id、name、input を持つ
- タイムアウトも再試行上限も無い。セッションは
cleanupPeriodDays(既定 30 日)の保持期間までディスクに残る
- そのターンで Claude がツール呼び出しを 1 つだけ行う場合のみ機能する。複数同時のときは警告付きで無視され通常の permission フローに進む
- resume 時に deferred なツールが利用できない場合、hook 発火前に
stop_reason: "tool_deferred_unavailable" と is_error: true で終了する
--resume は deferred 時の permission mode を復元する。例外は plan と bypassPermissions(決して引き継がれない)と auto(アカウントが要件を満たす場合のみ復元)
SessionStart の decision control
- このイベントでは stdout がそのまま Claude に届くため、コンテキストを読み込むだけの hook は JSON を組み立てず stdout に出力してよい
hook タイプ別の対応イベント
- 5 種類すべて(
command / http / mcp_tool / prompt / agent)に対応: PermissionDenied、PermissionRequest、PostToolBatch、PostToolUse、PostToolUseFailure、PreToolUse、Stop、SubagentStop、TaskCompleted、TaskCreated、TeammateIdle、UserPromptExpansion、UserPromptSubmit
command / http / mcp_tool のみ(prompt と agent 非対応): ConfigChange、CwdChanged、DirectoryAdded、Elicitation、ElicitationResult、FileChanged、InstructionsLoaded、Notification、PostCompact、PreCompact、SessionEnd、StopFailure、SubagentStart、WorktreeCreate、WorktreeRemove
SessionStart と Setup は command と mcp_tool のみ(http / prompt / agent 非対応)
prompt hook
- hook 入力とプロンプトを Claude モデル(既定は Haiku)に送る
- LLM が決定を含む構造化 JSON を返す
- Claude Code が決定を自動処理する
レスポンススキーマ: {"ok": true | false, "reason": "..."}。ok が false のとき reason は必須。
ok: false のイベント別の挙動:
Stop / SubagentStop: 理由が Claude の次の指示としてフィードバックされターンが続く
PreToolUse: ツール呼び出しが拒否される。既定ではターンが終わり拒否理由が警告行として表示される。continueOnBlock: true で理由をツールエラーとして Claude に返す
PostToolUse: 既定ではターンが終わり理由が警告行として表示される。continueOnBlock: true でフィードバックしターンを続ける
PostToolBatch / UserPromptSubmit / UserPromptExpansion: continue に関わらずターンが終わり理由が警告行になる
PostToolUseFailure / TaskCreated: continueOnBlock に関わらず理由がツールエラーとして返されターンが続く
TaskCompleted: ターン中にタスクが完了マークされて発火した場合はツールエラーとして返しターンが続く。teammate 停止で発火した場合は TeammateIdle と同様に既定で teammate を止める
TeammateIdle: 既定で teammate が止まり理由が警告行になる。continueOnBlock: true でフィードバックし作業を続けさせる
PermissionRequest: ok: false は効果が無い。拒否には command hook の hookSpecificOutput.decision.behavior: "deny" を使う
PermissionDenied: ok: false は効果が無い。このイベントが読むのは hookSpecificOutput.retry だけで、prompt / agent hook はそれを設定できない
agent hook(実験的)
- プロンプトと hook の JSON 入力を持つ subagent を起動する
- subagent は Read / Grep / Glob などのツールで調査できる
- 最大 50 ターンの後、構造化された
{ "ok": true/false } の決定を返す
- Claude Code は prompt hook と同じように決定を処理する
async hook
- command hook に
"async": true を付けるとブロックせずバックグラウンドで動く。このフィールドは type: "command" でのみ使える
- async hook は Claude の挙動をブロック・制御できない。
decision、permissionDecision、continue などのレスポンスフィールドは効果が無い
- hook プロセスは同じ JSON 入力を stdin で受け取る
- バックグラウンドプロセスの終了後、
additionalContext を含む JSON レスポンスがあれば、その内容が次の会話ターンでコンテキストとして Claude に届く。systemMessage はユーザーに表示される
- レスポンスは同期 hook と同じ出力スキーマで検証され、型の合わないフィールドは配信されず落とされる。
--debug で落とされたフィールド名の警告が見える
- 完了通知は既定で抑止される。
Ctrl+O の verbose モードまたは --verbose で表示される
- 制約
- 出力は次の会話ターンで配信される。セッションがアイドルなら次のユーザー操作まで待つ。例外は終了コード 2 の
asyncRewake hook で、アイドルでも即座に Claude を起こす
- 実行ごとに別のバックグラウンドプロセスが作られる。同じ async hook の複数回発火に対する重複排除は無い
Windows の PowerShell
- command hook に
"shell": "powershell" を設定すると個別の hook を PowerShell で実行できる。pwsh.exe(PowerShell 7 以降)を自動検出し、無ければ powershell.exe(Windows PowerShell 5.1)にフォールバックする
- PowerShell の shell form でプロジェクトルートを参照するには
${CLAUDE_PROJECT_DIR} または $env:CLAUDE_PROJECT_DIR と書く。v2.1.198 以降、${CLAUDE_PROJECT_DIR} / ${CLAUDE_PLUGIN_ROOT} / ${CLAUDE_PLUGIN_DATA} は PowerShell の ${env:NAME} 形式に書き換えられる。ダブルクォート内では展開されるがシングルクォート内では展開されない
- 素の
$CLAUDE_PROJECT_DIR は書かない。PowerShell は未定義のローカル変数として $null に解決する。Claude Code は書き換えず debug log に警告を記録する
デバッグ
- どの hook が一致したか、終了コード、stdout と stderr の全文はデバッグログに書かれる。
claude --debug-file <path> で任意の場所へ、claude --debug で ~/.claude/debug/<session-id>.txt に書く。--debug はターミナルには出力しない
CLAUDE_CODE_DEBUG_LOG_LEVEL=verbose で hook の matcher 数やクエリ照合などの詳細ログが追加される
設定
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(rm *)",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.sh",
"args": []
}
]
}
]
}
}
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Destructive command blocked by hook"
}
}
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/run-tests-async.sh",
"args": [],
"async": true,
"timeout": 300
}
]
}
]
}
}
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "http",
"url": "http://localhost:8080/hooks/pre-tool-use",
"timeout": 30,
"headers": { "Authorization": "Bearer $MY_TOKEN" },
"allowedEnvVars": ["MY_TOKEN"]
}
]
}
]
}
}
---
name: secure-operations
description: Perform operations with security checks
hooks:
PreToolUse:
- matcher: "Bash"
hooks:
- type: command
command: "./scripts/security-check.sh"
---
制約・注意点
- command hook はユーザーの完全な権限でシェルコマンドを実行する。設定に追加する前にレビューとテストを行う
- 公式に挙げられているベストプラクティス: 入力を検証・サニタイズする、シェル変数は常にクォートする(
"$VAR")、パストラバーサル(..)をブロックする、絶対パスを使う、.env や .git/ や鍵などの機微なファイルを避ける
- macOS と Linux では、v2.1.139 以降 command hook は制御端末を持たない独自のセッションで動く。hook プロセスとその子プロセスは
/dev/tty を開けず、エスケープシーケンスを直接 UI に送れない。ユーザーへのメッセージは systemMessage、通知やタイトルやベルは terminalSequence を使う
関連
facts/claude-code/settings.md
facts/claude-code/permissions.md
facts/claude-code/permission-modes.md
facts/claude-code/skills.md
facts/claude-code/sub-agents.md
facts/claude-code/mcp.md
facts/claude-code/tools-reference.md
facts/claude-code/worktrees.md
facts/claude-code/statusline.md
facts/claude-code/env-vars.md