フックのリファレンス
フックの全イベントの発火タイミング・matcher・入力フィールド・決定の制御、設定の書き方、終了コードと JSON 出力、各種ハンドラーのフィールドを一覧で引けます。
フック(hook)は、Claude Code のライフサイクルの決まった時点で自動実行される、ユーザー定義のシェルコマンド・HTTP エンドポイント・MCP ツール呼び出し・LLM プロンプト・サブエージェントです。ターミナル・IDE 拡張・デスクトップアプリ・クラウドセッションのどこで動かしても、同じフックイベントが発火します。このページは、イベントのスキーマ・設定のオプション・JSON の入出力・非同期や HTTP などの応用機能の引き先です。クイックスタートと実例はフックの使い方にあります。
プラグインは、フックを、Claude Code が自分のプロセスの中で呼ぶ JavaScript の関数としても登録でき、イベントに反応するだけでなく画面にも描けます。そうしたプラグインが Mod(モッド)で、関数のフックはこのページではなく Mod のリファレンスで扱います。このページのフックは、Mod と並んで引き続き動きます。
要点#
- 設定は「イベント → matcher グループ → ハンドラー」の3段の入れ子
- ハンドラーは
command・http・mcp_tool・prompt・agentの5種類 - 一致したハンドラーは並行して動く。同じハンドラーを複数の設定ファイルに書いても1回だけ動く
- 終了コード 2 でブロックでき、終了コード 0 と JSON で細かく制御できる
- 決定の書き方はイベントごとに違う(このページの「決定の制御」の表)
ライフサイクルとイベントの一覧#
イベントが発火して matcher が一致すると、Claude Code はそのイベントの JSON の文脈をハンドラーに渡します。コマンドフックでは stdin、HTTP フックでは POST のリクエストボディです。ハンドラーは入力を調べ、処理をして、必要なら判定を返します。
イベントの発火の頻度は3種類です。
- セッションごと:
SessionStartとSessionEnd - ターンごと:
UserPromptSubmit・Stop・StopFailure - エージェントのループ内のツール呼び出しごと:
PreToolUseとPostToolUse(EndConversationの呼び出しはどちらも飛ばす)
| イベント | 発火するとき |
|---|---|
SessionStart |
セッションの開始時・再開時 |
Setup |
--init-only で起動したとき、または -p モードで --init か --maintenance を付けたとき。CI やスクリプトでの1回限りの準備用 |
UserPromptSubmit |
プロンプトが送信されたとき(Claude が処理する前)。Claude Code が自分で始めるターンでも発火する |
UserPromptExpansion |
ユーザーが入力したコマンドがプロンプトに展開されるとき(Claude に届く前)。展開をブロックできる |
PreToolUse |
ツール呼び出しの実行前。ブロックできる |
PermissionRequest |
ツール呼び出しに権限の判定が要るとき |
PermissionDenied |
auto モードがツール呼び出しを拒否したとき(分類器の判定が無い拒否を含む)。JSON の hookSpecificOutput.retry: true で、拒否されたツール呼び出しをモデルが再試行してよいと伝えられる。分類器の判定が無いときは retry は無視される |
PostToolUse |
ツール呼び出しが成功した後 |
PostToolUseFailure |
ツール呼び出しが失敗した後 |
PostToolBatch |
並行したツール呼び出しの1バッチがすべて解決した後、次のモデル呼び出しの前 |
Notification |
Claude Code が通知を送るとき |
MessageDisplay |
アシスタントのメッセージのテキストが表示される間 |
SubagentStart |
サブエージェントが起動されるとき |
SubagentStop |
サブエージェントが終わるとき |
TaskCreated |
TaskCreate でタスクが作られようとしているとき |
TaskCompleted |
タスクが完了としてマークされようとしているとき |
Stop |
Claude が応答を終えたとき |
StopFailure |
API エラーでターンが終わったとき |
TeammateIdle |
エージェントチームのチームメイトがアイドルになろうとしているとき |
InstructionsLoaded |
CLAUDE.md や .claude/rules/*.md が文脈に読み込まれたとき。セッション開始時と、セッション中に遅延読み込みされたとき |
ConfigChange |
セッション中に設定ファイルが変わったとき |
CwdChanged |
作業ディレクトリが変わったとき(Claude が cd を実行したときなど)。direnv のようなツールによる、環境の反応的な管理に使える |
DirectoryAdded |
/add-dir か SDK の register_repo_root の制御リクエストで、作業ディレクトリが途中で追加されたとき |
FileChanged |
監視しているファイルがディスク上で変わったとき。matcher で監視するファイル名を指定する |
WorktreeCreate |
--worktree・isolation: "worktree"・バックグラウンドセッションで worktree が作られようとしているとき。既定の git の動作を置き換える |
WorktreeRemove |
セッション終了時・サブエージェントの終了時・バックグラウンドセッションの削除時に worktree が削除されようとしているとき |
PreCompact |
コンテキストのコンパクションの前 |
PostCompact |
コンパクションが完了した後 |
PreModelSwitch |
あなたやクライアントが要求したモデルの切り替えを Claude Code が適用する前。切り替えをブロックできる |
PostModelSwitch |
セッションのモデルが変わった後(セッションを再開したときのモデルの復元など、Claude Code 自身が行う変更も含む) |
Elicitation |
MCP サーバーがツール呼び出しの途中でユーザーの入力を求めたとき |
ElicitationResult |
ユーザーが MCP の elicitation に答えた後、サーバーへ送り返す前 |
SessionEnd |
セッションが終了するとき |
フックが解決される流れ#
破壊的なシェルコマンドをブロックする PreToolUse フックの例です。
matcher で Bash のツール呼び出しに絞り、if でさらに rm * に一致する Bash のサブコマンドに絞るので、block-rm.sh は両方に一致したときだけ起動します(macOS・Linux)。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(rm *)",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.sh",
"args": []
}
]
}
]
}
}
スクリプトは stdin の JSON を読み、コマンドを取り出して、rm -rf を含んでいれば permissionDecision を "deny" にして返します。プロジェクトの .claude/hooks/block-rm.sh に保存し、chmod +x で実行権限を付けます。
#!/bin/bash
# .claude/hooks/block-rm.sh
COMMAND=$(jq -r '.tool_input.command')
if echo "$COMMAND" | grep -q 'rm -rf'; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "Destructive command blocked by hook"
}
}'
else
exit 0 # no decision; normal permission flow applies
fi
Windows(PowerShell)では、matcher を Bash|PowerShell にして、PowerShell ツールも対象にします。1つの if ルールは1つのツールの呼び出しにしか一致しないので、ツールごとにハンドラーを分けます(Bash 用は Bash(rm *)、PowerShell 用は PowerShell(Remove-Item *))。どちらも同じスクリプトを powershell.exe で動かします。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash|PowerShell",
"hooks": [
{
"type": "command",
"if": "Bash(rm *)",
"command": "powershell.exe",
"args": [
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
"${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.ps1"
]
},
{
"type": "command",
"if": "PowerShell(Remove-Item *)",
"command": "powershell.exe",
"args": [
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
"${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.ps1"
]
}
]
}
]
}
}
-NoProfile は PowerShell のプロファイルを読み込まずフックを速く起動し、-ExecutionPolicy Bypass はローカルのスクリプトファイルを実行できるようにします。
# .claude/hooks/block-rm.ps1
$callInput = [Console]::In.ReadToEnd() | ConvertFrom-Json
$command = $callInput.tool_input.command
if ($command -match 'rm -rf|Remove-Item.*-Recurse') {
@{
hookSpecificOutput = @{
hookEventName = "PreToolUse"
permissionDecision = "deny"
permissionDecisionReason = "Destructive command blocked by hook"
}
} | ConvertTo-Json
} else {
exit 0 # no decision; normal permission flow applies
}
macOS・Linux の設定で、Claude Code が Bash "rm -rf /tmp/build" を実行しようとしたときの流れは次のとおりです。
- イベントの発火:
PreToolUseが発火し、ツールの入力が JSON で stdin に渡る({ "tool_name": "Bash", "tool_input": { "command": "rm -rf /tmp/build" }, ... }) - matcher の判定:
"Bash"がツール名に一致し、このフックグループが有効になる。matcher を省くか"*"にすると、グループはそのイベントの発生のたびに有効になる ifの判定:"Bash(rm *)"は、rm -rf /tmp/buildがrm *に一致するサブコマンドなので一致し、このハンドラーが起動する。npm testだったらifが一致せず、block-rm.shは起動せず、プロセス起動のコストも避けられる。ifは任意で、無ければ一致したグループのハンドラーはすべて動く- ハンドラーの実行:スクリプトが全コマンドを調べて
rm -rfを見つけ、判定を stdout に出す。rm file.txtのように安全なrmならexit 0に当たる。終了コード 0 で出力が無いときは、フックは報告する判定が無いということで、ツール呼び出しは通常の権限の流れを進む。フックは呼び出しを拒否できるが、黙っているのは承認ではない - 結果の反映:Claude Code が JSON の判定を読み、ツール呼び出しをブロックして、理由を Claude に見せる
設定#
フックは JSON の設定ファイルで定義します。設定は3段の入れ子です。
- 応答するフックイベントを選ぶ(
PreToolUse・Stopなど) - 発火の条件を絞る matcher グループを足す(「Bash ツールのときだけ」など)
- 一致したときに動くフックハンドラーを1つ以上定義する
補足
このページでは、ライフサイクルの時点を「フックイベント」、絞り込みを「matcher グループ」、動くシェルコマンド・HTTP エンドポイント・MCP ツール・プロンプト・エージェントを「フックハンドラー」と呼びます。単に「フック」と言うときは、機能全般を指します。
フックの置き場所#
| 場所 | 範囲 | 共有できるか |
|---|---|---|
~/.claude/settings.json |
自分の全プロジェクト | いいえ(自分のマシンだけ) |
.claude/settings.json |
1つのプロジェクト | はい(リポジトリにコミットできる) |
.claude/settings.local.json |
1つのプロジェクト | いいえ(Claude Code が設定を保存するときに gitignore される) |
| 管理ポリシー設定 | 組織全体 | はい(管理者が制御) |
プラグインの hooks/hooks.json |
プラグインが有効な間 | はい(プラグインに同梱) |
| スキルの frontmatter | スキルを呼んだ後のセッションの残り | はい(スキルのファイルに定義) |
| サブエージェントの frontmatter | そのサブエージェントが動いている間 | はい(サブエージェントのファイルに定義) |
- クラウドセッションは、手元の
~/.claude/settings.jsonを読みません。セルフホスト環境では、運用者がランナーホストの~/.claude/から入れたフックと、ランナーイメージの管理設定ファイルのフックも動きます(管理設定のその層が適用される場合)。クラウドセッションにどの設定とプラグインが届くかは、クラウド(Web)を参照してください - 設定ファイル・管理ポリシー設定・プラグインのフックは、サブエージェントの中でも動きます。サブエージェントがツールを呼ぶと、
PreToolUseやPostToolUseなどのツールイベントが、メインの会話と同じ設定のフックを発火し、入力にはサブエージェントを識別するagent_idとagent_typeが入ります - 管理者は、管理設定の
allowManagedHooksOnlyで、動かせるフックを制限できます- user・project・local・プラグインのフックがブロックされる(管理設定の
enabledPluginsで強制有効にしたプラグインのフックは除く) statusLine・fileSuggestion・subagentStatusLineの設定も管理設定に限られるcommandソースのプラグインも、disableCommandPluginSourcesを明示的にfalseにしない限り無効になる(管理設定のenabledPluginsで強制有効にしたものを含む)。commandソースは v2.1.229 以降- マーケットプレイスの
headersHelperコマンドも、disableCommandPluginSourcesを明示的にfalseにしない限りブロックされる(管理設定自身が宣言したマーケットプレイスは除く)
- user・project・local・プラグインのフックがブロックされる(管理設定の
- フックのエントリは、設定の階層をまたいで置き換えられず統合されます。user・project・local の設定は管理設定のフックを消さずに自分のフックを足し、
disableAllHooksは管理設定の外から管理フックを無効にできません - HTTP フックの許可リストは、管理ポリシー設定を含むすべての元のフックに適用されます
allowedHttpHookUrls:どの設定の階層でも定義されていると、統合した許可リストに URL が一致する HTTP ハンドラーだけが動くhttpHookAllowedEnvVars:定義されていると、そのリストの環境変数だけがフックのヘッダーに展開される
matcher のパターン#
matcher フィールドで、フックが発火する条件を絞ります。評価のしかたは、含まれる文字で決まります。
| matcher の値 | 評価のされ方 | 例 |
|---|---|---|
"*"・""・省略 |
すべてに一致 | イベントの発生のたびに発火する |
英字・数字・_・-・空白・,・| だけ |
完全一致の文字列、または | か ,(前後の空白は可)で区切った完全一致の文字列の一覧 |
Bash は Bash ツールだけに一致。Edit|Write と Edit, Write はどちらも2つのツールのどちらかに完全一致。code-reviewer はそのエージェントの種類だけに一致 |
| それ以外の文字を含む | JavaScript の正規表現(アンカーなし) | ^Notebook は名前が Notebook で始まるツールに一致。mcp__memory__.* は memory サーバーの全ツールに一致 |
- 正規表現の経路の matcher は、JavaScript の
RegExp.prototype.testで評価され、値のどこかに一致すれば成功します。Edit.*はEditとNotebookEditの両方に一致します。全体一致が要るときは、^Edit$のように^と$で囲みます FileChangedとStopFailureは、英字・数字・_・|だけの、より狭い完全一致の集合を使います。この2つのイベントで、ハイフン・空白・カンマを matcher に入れると、正規表現の経路になり、選択肢を区切れるのは|だけです。以下の表にある、matcher に対応するほかのイベントは、|か,を受け付けますFileChangedは、監視リストを作るときにはこれらの規則に従いません(FileChangedの節を参照)matcherを、matcher に対応しないイベントに足しても、黙って無視されます
イベントごとに、matcher が見るフィールドは違います。
| イベント | matcher が絞るもの | matcher の値の例 |
|---|---|---|
PreToolUse・PostToolUse・PostToolUseFailure・PermissionRequest・PermissionDenied |
ツール名 | Bash・Edit|Write・mcp__.* |
SessionStart |
セッションの始まり方 | startup・resume・clear・compact・fork |
Setup |
準備を起こした CLI フラグ | init・maintenance |
SessionEnd |
セッションが終わった理由 | clear・resume・logout・prompt_input_exit・other |
Notification |
通知の種類 | permission_prompt・idle_prompt・auth_success・elicitation_dialog・elicitation_url_dialog・elicitation_complete・elicitation_response・agent_needs_input・agent_completed・quota_auto_resume_fired・quota_auto_resume_stale・quota_auto_resume_disabled |
SubagentStart |
エージェントの種類 | general-purpose・Explore・Plan・カスタムのエージェント名・^my-plugin:reviewer$ のようなプラグインのスコープ付きの名前 |
PreCompact・PostCompact |
コンパクションを起こしたもの | manual・auto |
PreModelSwitch・PostModelSwitch |
セッションが切り替わる先のモデルの正規名(PreModelSwitch の節を参照) |
claude-opus-5・claude-opus-4-6|claude-opus-5・.*opus.* |
SubagentStop |
エージェントの種類 | SubagentStart と同じ値 |
ConfigChange |
設定の出どころ | user_settings・project_settings・local_settings・policy_settings・skills |
CwdChanged |
matcher 非対応 | 発生のたびに常に発火する |
DirectoryAdded |
ディレクトリが追加された方法 | slash_command・register_repo_root |
FileChanged |
監視するリテラルのファイル名(FileChanged の節を参照) |
.envrc|.env |
StopFailure |
エラーの種類 | rate_limit・overloaded・authentication_failed・oauth_org_not_allowed・account_on_hold・billing_error・invalid_request・model_not_found・server_error・max_output_tokens・cloud_credential_error・unknown |
InstructionsLoaded |
読み込みの理由 | session_start・nested_traversal・path_glob_match・include・compact |
UserPromptExpansion |
コマンド名 | 自分のスキル名やコマンド名 |
Elicitation |
MCP サーバー名 | 設定した MCP サーバー名 |
ElicitationResult |
MCP サーバー名 | Elicitation と同じ値 |
UserPromptSubmit・PostToolBatch・Stop・TeammateIdle・TaskCreated・TaskCompleted・WorktreeCreate・WorktreeRemove・MessageDisplay |
matcher 非対応 | 発生のたびに常に発火する |
StopFailureをcloud_credential_errorで一致させるには、v2.1.267 以降が必要です。認証情報の読み込みの失敗を、server_errorやunknownではなくこの値で報告する最初の版です- ほとんどのイベントでは、Claude Code は stdin で送る JSON 入力のフィールドに対して matcher を評価します。ツールのイベントでは
tool_nameです。PreModelSwitchとPostModelSwitchでは、to_modelから導いた正規名に対して評価します
Claude が書き込みや編集をしたときだけ、lint のスクリプトを動かす例:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "/path/to/lint-check.sh"
}
]
}
]
}
}
ツールのイベントでは、個々のハンドラーの if フィールドでさらに絞れます。if は権限ルールの構文で、ツール名と引数をあわせて照合します。"Bash(git *)" は Bash の入力のどれかのサブコマンドが git * に一致したときに動き、"Edit(*.ts)" は TypeScript のファイルだけで動きます。
MCP ツールに一致させる#
MCP サーバーのツールは、ツールのイベント(PreToolUse・PostToolUse・PostToolUseFailure・PermissionRequest・PermissionDenied)で、普通のツールとして現れるので、ほかのツール名と同じように一致させられます。MCP ツールの名前は mcp__<server>__<tool> の形です。
mcp__memory__create_entities:Memory サーバーの create entities ツールmcp__filesystem__read_file:Filesystem サーバーの read file ツールmcp__github__search_repositories:GitHub サーバーの検索ツール
サーバーの全ツールに一致させるには、サーバーの接頭辞に .* を付けます。.* は必須です。mcp__memory や mcp__brave-search のような matcher は完全一致の文字だけで、文字列として完全一致で比べられ、どのツールにも一致しません。
mcp__memory__.*はmemoryサーバーの全ツールに一致するmcp__brave-search__.*は、名前にハイフンを含むサーバーの全ツールに一致するmcp__.*__write.*は、どのサーバーでも、名前がwriteで始まるツールに一致する
プラグインに同梱された MCP サーバーのツールは、プラグイン名を含むスコープ付きのサーバー部分を使い、mcp__plugin_<plugin-name>_<server-name>__<tool> の形です。素のサーバーキーで書いた matcher は、これらのツールでは発火しません。my-plugin が db というキーでサーバーを同梱していれば、query ツールは mcp__plugin_my-plugin_db__query で、そのサーバーの全ツールの matcher は mcp__plugin_my-plugin_db__.* です。ハンドラーの if フィールドにも同じスコープ付きのツール名を使います。
memory サーバーの全操作を記録し、どの MCP サーバーの書き込み操作も検証する例:
{
"hooks": {
"PreToolUse": [
{
"matcher": "mcp__memory__.*",
"hooks": [
{
"type": "command",
"command": "echo 'Memory operation initiated' >> ~/mcp-operations.log"
}
]
},
{
"matcher": "mcp__.*__write.*",
"hooks": [
{
"type": "command",
"command": "/home/user/scripts/validate-mcp-write.py"
}
]
}
]
}
}
ハンドラーの種類#
内側の hooks 配列の各オブジェクトが、matcher が一致したときに動くフックハンドラーです。5種類あります。
| 種類 | type |
動作 |
|---|---|---|
| コマンドフック | command |
シェルコマンドを実行する。スクリプトはイベントの JSON を stdin で受け取り、終了コードと stdout で結果を返す |
| HTTP フック | http |
イベントの JSON を URL へ HTTP POST で送る。エンドポイントは、コマンドフックと同じ JSON 出力形式でレスポンスボディに結果を返す |
| MCP ツールフック | mcp_tool |
設定済みの MCP サーバーのツールを呼ぶ。ツールのテキスト出力は、コマンドフックの stdout と同じに扱われる |
| プロンプトフック | prompt |
Claude のモデルにプロンプトを送り、1回で評価させる。モデルは判定を JSON で返す |
| エージェントフック | agent |
Read・Grep・Glob などのツールを使えるサブエージェントを起動し、条件を確かめてから判定を返す。実験的で変更されうる |
- 一致したフックはすべて並行して動きます。同じハンドラーを複数の設定ファイルに定義しても1回だけ動きます。プラグインやスキルの同じハンドラーは別扱いです
- ハンドラーは、現在のディレクトリで Claude Code の環境のまま動きます。現在のディレクトリが存在しなくなっている場合(別のシェルが削除した worktree や一時ディレクトリなど)、Claude Code は、まだ存在する次のうち最初のものからコマンドフックを動かします:セッションを始めたディレクトリ・プロジェクトルート・ホームディレクトリ・システムの一時ディレクトリ。代替のディレクトリを名指しする警告をデバッグログに記録します
- 環境変数
$CLAUDE_CODE_REMOTEは、リモートの Web 環境では"true"で、ローカルの CLI では設定されません。v2.1.199 以降は、ローカルのセッションに有効なリモートコントロール接続がある間、$CLAUDE_CODE_BRIDGE_SESSION_IDにリモートコントロールのセッション ID を設定します
共通のフィールド#
全種類のハンドラーに共通です。
| フィールド | 必須 | 内容 |
|---|---|---|
type |
はい | "command"・"http"・"mcp_tool"・"prompt"・"agent" |
if |
いいえ | このフックが動く条件を絞る権限ルール構文("Bash(git *)"・"Edit(*.ts)" など)。ツール呼び出しが一致したときだけ、フックのコマンドが動く。ツールのイベント(PreToolUse・PostToolUse・PostToolUseFailure・PermissionRequest・PermissionDenied)でだけ評価される。それ以外のイベントでは、if を設定したフックは動かない |
timeout |
いいえ | 取り消すまでの秒数。async: true で動かすコマンドフックには強制されない。既定は command・http・mcp_tool が 600、prompt が 30、agent が 60。command・http・mcp_tool の既定は、UserPromptSubmit・PreModelSwitch・PostModelSwitch では 30、MessageDisplay では 10 に下がる。SessionEnd のフックは 1.5 秒の予算を共有し、設定がそれより長いフックごとの timeout を指定していれば、予算が合わせて引き上げられる(上限 60 秒) |
statusMessage |
いいえ | フックの実行中に出るスピナーのメッセージ |
once |
いいえ | true なら、最初に成功した実行の後でフックが取り除かれる。失敗・終了コード 2 のブロック・タイムアウトの実行ではフックが残り、次に一致するイベントで再び動く。スキルの frontmatter に宣言したフックでだけ有効で、設定ファイルとエージェントの frontmatter では無視される |
ifフィールドには、権限ルールをちょうど1つだけ書けます。&&・||・リストで組み合わせる構文は無く、複数の条件を使うには、条件ごとに別のハンドラーを定義します- ファイルツールの
ifでは、"Edit(src/**)"のような1階層のディレクトリのパターンは、作業ディレクトリ内のsrcディレクトリとその下のファイルだけに一致します。どの深さにあるsrcディレクトリにも一致させるには、"Edit(**/src/**)"と書きます。v2.1.214 より前は、"Edit(src/**)"が作業ディレクトリ以下のどの深さのsrcにも一致していました
if のパターンが Bash コマンドに一致するしくみ#
if フィールドの Bash のパターンで、フックのコマンドが動くかは、パターンの形と Claude が呼ぶ Bash コマンドで決まります。先頭の VAR=value の代入は、照合の前に取り除かれます。
if のパターン |
Bash コマンド | フックが動くか | 理由 |
|---|---|---|---|
Bash(git *) |
FOO=bar git push |
動く | 先頭の代入が取り除かれ、git push が一致 |
Bash(git *) |
npm test && git push |
動く | 各サブコマンドが調べられ、git push が一致 |
Bash(rm *) |
echo $(rm -rf /) |
動く | $() とバッククォートの中のコマンドも調べられ、rm -rf / が一致 |
Bash(rm *) |
echo $(date) |
動かない | rm * に一致するサブコマンドが無い |
Bash(git push *) |
echo $(date) |
動く | コマンド名以上を指定するパターンは、$()・バッククォート・$VAR があると、とにかくフックを動かす |
Bash の入力がどのコマンドを実行するか Claude Code が判定できないときは、パターンに関係なくフックを動かします。if の絞り込みは最善努力なので、許可や拒否を確実に強制するにはフックでなく権限を使います。
コマンドフックのフィールド#
共通のフィールドに加えて、次のフィールドがあります。
| フィールド | 必須 | 内容 |
|---|---|---|
command |
はい | 実行するシェルコマンド。args があるときは、直接起動する実行ファイル |
args |
いいえ | 引数のリスト。あると、command は実行ファイルとして解決され、args を引数のベクトルとして、シェルを介さず直接起動される |
async |
いいえ | true なら、ブロックせずバックグラウンドで動く(後述「バックグラウンドでフックを動かす」) |
asyncRewake |
いいえ | true なら、バックグラウンドで動き、終了コード 2 で Claude を起こす。フックの stderr(空なら stdout)がシステムリマインダーとして Claude に見え、長く動くバックグラウンドの失敗に反応できる |
shell |
いいえ | このフックに使うシェル。"bash" か "powershell"。既定は "bash"。Windows で Git Bash が入っていなければ "powershell"。"powershell" にすると Windows で PowerShell 経由でコマンドを動かす。フックは PowerShell を直接起動するので、CLAUDE_CODE_USE_POWERSHELL_TOOL は要らない。args があると無視される |
exec 形式とシェル形式
コマンドフックは、args があれば exec 形式、無ければシェル形式で動きます。フックがパスのプレースホルダーを参照するときは、各要素が引用符なしで1つの引数として渡されるので、args を設定します。パイプや && などのシェルの機能が要るとき、またはどちらの心配も無いときは、args を省きます。
- exec 形式(
argsあり):Claude Code はcommandをPATH上の実行ファイルとして解決し、argsを引数のベクトルとして直接起動します。シェルは無いので、argsの各要素は書いたとおり1つの引数になり、${CLAUDE_PLUGIN_ROOT}のようなパスのプレースホルダーは、commandと各args要素に、単なる文字列として置換されます。アポストロフィ・$・バッククォートなどの特殊文字は、解釈するシェルが無いので、そのまま渡ります。どのプラットフォームでも、シェルによるトークン分割は起きません - シェル形式(
argsなし):commandの文字列がシェルに渡されます。macOS と Linux はsh -c、Windows は Git Bash(無ければ PowerShell)です。shellフィールドで明示的にも選べます。シェルが文字列を分割し、変数を展開し、パイプ・&&・リダイレクト・glob を解釈します
補足
Windows の exec 形式では、command が .exe のような本物の実行ファイルに解決される必要があります。npm・npx・eslint などが node_modules/.bin に入れる .cmd と .bat のシムは実行ファイルではなく、シェルなしでは起動できません。exec 形式で動かすには、node で元のスクリプトを直接呼びます(例:"command": "node", "args": ["${CLAUDE_PLUGIN_ROOT}/node_modules/eslint/bin/eslint.js"])。node とスクリプトのパスの形は、node.exe が本物のバイナリなので、どのプラットフォームでも動きます。.cmd や .bat のシムを名前で動かすには、シェル形式を使います。
プラグインに同梱した Node スクリプトを動かす例です。exec 形式なら、解決されたスクリプトのパスが、引用符なしで1つの引数として渡されます。
{
"type": "command",
"command": "node",
"args": ["${CLAUDE_PLUGIN_ROOT}/scripts/format.js", "--fix"]
}
同じ内容のシェル形式では、空白や特殊文字を含むパスに対応するための引用符が要ります。
{
"type": "command",
"command": "node \"${CLAUDE_PLUGIN_ROOT}\"/scripts/format.js --fix"
}
- どちらの形式も同じパスのプレースホルダーに対応し、起動したプロセスの環境変数
CLAUDE_PROJECT_DIR・CLAUDE_PLUGIN_ROOT・CLAUDE_PLUGIN_DATAとしても書き出します。スクリプトは、どう起動されたかにかかわらずprocess.env.CLAUDE_PLUGIN_ROOTを読めます - プラグインのフックは、exec 形式に限り、
${user_config.*}の値も置換します(commandと各args要素に単なる文字列として置換されるので、シェルが再解析しない) ${user_config.*}をcommandに書いたシェル形式のプラグインフックは、実行されずエラーになります。シェル形式のフックでオプションの値を使うには、環境変数$CLAUDE_PLUGIN_OPTION_<KEY>(webhook_urlオプションなら$CLAUDE_PLUGIN_OPTION_WEBHOOK_URL)を読むか、argsを設定して exec 形式に切り替えます。v2.1.207 より前は、シェル形式のプラグインフックのコマンドも${user_config.*}を置換していました
補足
exec 形式では、command は実行ファイルの名前かパスだけです。command がパスの区切りを持たない素の名前で、args と一緒に空白を含んでいると、起動が失敗するので警告がログに出ます(node script.js という名前の実行ファイルは無い)。余分なトークンは args に移します。C:\Program Files\nodejs\node.exe のように空白を含む絶対パスは、有効な1つの実行ファイルとして、警告になりません。
HTTP フックのフィールド#
共通のフィールドに加えて、次のフィールドがあります。
| フィールド | 必須 | 内容 |
|---|---|---|
url |
はい | POST リクエストの送信先の URL |
headers |
いいえ | 追加の HTTP ヘッダーのキーと値。値は $VAR_NAME か ${VAR_NAME} の形で環境変数を展開できる。展開されるのは allowedEnvVars に挙げた変数だけ |
allowedEnvVars |
いいえ | ヘッダーの値に展開してよい環境変数名のリスト。挙げていない変数への参照は空文字列に置き換わる。環境変数の展開を使うには必須 |
Claude Code は、フックの JSON 入力を Content-Type: application/json の POST リクエストボディとして送ります。レスポンスボディは、コマンドフックと同じ JSON 出力形式です。エラーの扱いはコマンドフックと違います(後述「HTTP のレスポンスの扱い」)。PreToolUse イベントをローカルの検証サービスに送り、MY_TOKEN 環境変数のトークンで認証する例:
{
"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"]
}
]
}
]
}
}
MCP ツールフックのフィールド#
共通のフィールドに加えて、次のフィールドがあります。
| フィールド | 必須 | 内容 |
|---|---|---|
server |
はい | 設定済みの MCP サーバーの名前。プラグインに同梱されたサーバーでは、素のサーバーキーでなく、スコープ付きの名前 plugin:<plugin-name>:<server-name>(plugin:my-plugin:db など) |
tool |
はい | そのサーバーで呼ぶツールの名前 |
input |
いいえ | ツールに渡す引数。文字列の値は、フックの JSON 入力から ${path} 形式の置換ができる("${tool_input.file_path}" など) |
Write か Edit のたびに、my_server MCP サーバーの security_scan ツールを、編集したファイルのパスを渡して呼ぶ例:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "mcp_tool",
"server": "my_server",
"tool": "security_scan",
"input": { "file_path": "${tool_input.file_path}" }
}
]
}
]
}
}
- ツールの結果の読まれ方:Claude Code は、ツールのテキスト内容を、コマンドフックの stdout と同じ(終了コード 0 の解析規則)に読みます。ツールが
isError: trueを返すと、フックはブロックしないエラーを出し、実行は続きます - サーバーがまだ接続中のとき:
PreToolUseやStopのように、フックがブロックしたり結果を変えたりできるイベントでは、Claude Code は、接続中のサーバーがつながるまで、最大MCP_TIMEOUTかつフック自身のtimeoutの範囲で待ってから、ツールを呼びます。NotificationやSessionEndのような観察のイベントでは待ちません。cachedの状態のサーバーは、フックがそのツールを呼ぶときに接続されます。その時点でサーバーがつながっていなければ、フックはブロックしないエラーを出し、実行は続きます。フックが OAuth の流れを始めることは無いので、先に/mcpでサーバーを認証します - MCP サーバーが使える前に発火するイベント:起動時の
SessionStart(--continueや--resumeの場合も)と、すべてのSetupイベントは、セッションの MCP サーバーがフックから使える前に発火します。Claude Code は、そのmcp_toolフックを、ツールを呼ばずに飛ばし、デバッグログにmcp_tool hooks are not available for the 'SessionStart' hook event (no MCP client context)(Setupなら、その名前を挙げた同じメッセージ)を記録します。/clearやコンパクションの後など、SessionStartがセッションの後のほうで再び発火するときは、そのmcp_toolフックが動きます。起動時にセッションが必要とするものには、SessionStartのtype: "command"フックを使います
プロンプトフックとエージェントフックのフィールド#
共通のフィールドに加えて、次のフィールドがあります。
| フィールド | 必須 | 内容 |
|---|---|---|
prompt |
はい | モデルに送るプロンプトのテキスト。$ARGUMENTS が、フックの入力 JSON のプレースホルダー。リテラルのテキストを入れるにはバックスラッシュで逃がす(\$1.00 は $1.00 と出る) |
model |
いいえ | 評価に使うモデル。既定は、Claude Code がバックグラウンドの機能に使うモデル |
スクリプトをパスで参照する#
フックが動くときの作業ディレクトリにかかわらず、プロジェクトやプラグインのルートからの相対でフックのスクリプトを参照するプレースホルダーです。
${CLAUDE_PROJECT_DIR}:セッションが始まったプロジェクトルート。Claude Code は、stdio の MCP サーバーとプラグインの LSP サーバーの環境にも、この変数を設定します${CLAUDE_PLUGIN_ROOT}:プラグインに同梱したスクリプト用の、プラグインのインストール先のディレクトリ。更新をまたいだパスの挙動は、プラグインのリファレンスにあります${CLAUDE_PLUGIN_DATA}:更新後も残すべき依存関係と状態のための、プラグインの永続データディレクトリ
補足
worktree は別です。セッション中に Claude がworktreeに入っても、Claude Code は ${CLAUDE_PROJECT_DIR} を元のままにし、worktree のパスは別の方法でフックに渡します。
${CLAUDE_PROJECT_DIR}は動かない:セッションが始まったプロジェクトルートを指したままなので、${CLAUDE_PROJECT_DIR}/.claude/hooks/check-style.shのようなコマンドは、メインのチェックアウトのスクリプトを動かし続けるcwdは Claude に従う:フックの入力 JSON のcwdは、Claude が worktree に入った後は worktree のルート、Claude がcdを実行した後は新しいディレクトリ。フックが、Claude がどのディレクトリで作業しているかを知る必要があるときは、これを読む
パスのプレースホルダーを参照するフックには、exec 形式を勧めます。シェル形式では、各プレースホルダーを二重引用符で囲みます。
プロジェクトのスクリプト:Write か Edit のツール呼び出しの後に、プロジェクトの .claude/hooks/ からスタイルチェッカーを動かす例です。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/check-style.sh",
"args": []
}
]
}
]
}
}
プラグインのスクリプト:プラグインのフックは、任意の最上位の description フィールドを持つ hooks/hooks.json に定義します。プラグインが有効になると、そのフックは、あなたのユーザーとプロジェクトのフックと統合されます。プラグインに同梱した整形スクリプトを動かす例です(作り方はプラグインを作って配る参照)。
{
"description": "Automatic code formatting",
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/format.sh",
"args": [],
"timeout": 30
}
]
}
]
}
}
スキルとエージェントのフック#
設定ファイルとプラグインのほかに、スキルとサブエージェントの frontmatter に、設定ベースのフックと同じ形式で、フックを直接定義できます。Claude Code がそれを登録しておく期間は、コンポーネントで違います。
- サブエージェントのフック:そのサブエージェントが動いている間だけ動き、終わると取り除かれる。ここの
Stopフックは、サブエージェントの完了時に発火するSubagentStopに変換される - スキルのフック:あなたか Claude がスキルを呼んだときに登録され、セッションの残りの間、スキル自身のターンより後のターンでも動き続ける。最初に成功した実行の後に取り除かれるようにするには、フックに
once: trueを付ける
各 Bash コマンドの前にセキュリティ検証スクリプトを動かす PreToolUse フックを定義するスキルの例:
---
name: secure-operations
description: Perform operations with security checks
hooks:
PreToolUse:
- matcher: "Bash"
hooks:
- type: command
command: "./scripts/security-check.sh"
---
サブエージェントも、YAML の frontmatter で同じ形式を使います。
- プロジェクトのスキルの frontmatter のフックは、設定ファイルのフックと同じワークスペースの信頼の規則に従います。Claude Code は、あなたか Claude がスキルを呼んだときに、信頼していないフォルダでの
-pの実行を含めて、それらを登録します - プロジェクトのサブエージェントの frontmatter のフックは、エージェントのファイルがあるフォルダの、ワークスペースの信頼ダイアログを承認した後にだけ動きます。
-pのセッションは、承認したことになりません。詳しくはサブエージェントを参照してください。v2.1.218 より前は、これらのフックは信頼していないフォルダからも動くことがありました
/hooks メニュー#
Claude Code で /hooks を入力すると、設定済みのフックの読み取り専用のブラウザが開きます。一覧は、各フックに、ユーザー設定・プロジェクト設定・ローカル設定・プラグイン・現在のセッションなど、出どころを示すラベルを付けます。
フックを選ぶと、それが動かすものの全文と、定義された場所(設定ファイルのパスやプラグインの名前など)が見られます。
設定されたフックが無いイベントも含めて、すべてのフックイベントを見るには、一覧の末尾の All events を選びます。
フックを無効にする・取り除く#
- 設定ファイルで定義したフックを取り除くには、そのファイルからエントリを削除します
- すべてのフックを、取り除かずに一時的に無効にするには、設定ファイルに
"disableAllHooks": trueを書きます。Claude Code は、設定の優先順位を適用した後に残る値を読むので、プロジェクトの.claude/settings.jsonの"disableAllHooks": falseは、ユーザー設定のtrueを上書きします。プロジェクトの設定に関係なく、1回の実行だけフックを切るには、--settings '{"disableAllHooks": true}'を渡します(プロジェクトとローカルの設定より優先されます)。個々のフックを、設定に残したまま無効にする方法はありません disableAllHooksは、管理設定の階層を尊重します。管理者が管理ポリシー設定でフックを設定している場合、user・project・local の設定のdisableAllHooksでは、それらの管理フックを無効にできません。管理フックを無効にできるのは、管理設定のレベルで設定したdisableAllHooksだけです- 設定ファイルのフックの直接の編集は、通常、ファイルの監視が自動で拾います
フックの入力と出力#
コマンドフックは、JSON のデータを stdin で受け取り、終了コード・stdout・stderr で結果を返します。HTTP フックは、同じ JSON を POST のリクエストボディで受け取り、HTTP のレスポンスボディで結果を返します。この節は、全イベントに共通のフィールドと挙動を扱います。各イベントの入力スキーマと決定の制御は、イベントごとの節にあります。
- macOS と Linux では、コマンドフックは、制御端末の無い自分のセッションで動きます。フックのプロセスと子プロセスは、
/dev/ttyを開けず、エスケープシーケンスを Claude Code の画面へ直接送れません。Windows には/dev/ttyがありません - どのプラットフォームでも、ユーザーにメッセージを出すには、JSON 出力で
systemMessageを返します(イベントによっては、これを捨てるか、別の場所へ届けます。各イベントの節に書いてあります)。デスクトップ通知・ウィンドウタイトルの設定・ベルには、代わりにterminalSequenceを返します
共通の入力フィールド#
イベントは、各イベントの節に書かれたイベント固有のフィールドのほかに、次のフィールドを JSON で受け取ります。
| フィールド | 内容 |
|---|---|
session_id |
現在のセッションの識別子 |
prompt_id |
いま処理中のユーザーのプロンプトを識別する UUID。OpenTelemetry のイベントの prompt.id 属性と一致するので、1つのプロンプトについてフックの出力とテレメトリを突き合わせられる。最初のユーザー入力までは無い。v2.1.196 以降 |
transcript_path |
会話の JSON のパス。記録ファイルは非同期に書かれ、メモリ上の会話より遅れることがあるので、フックが発火した時点では、現在のターンの最新のメッセージがまだ入っていないことがある。現在のターンの最後のアシスタントのテキストが要るフックは、記録を読まず、Stop と SubagentStop の last_assistant_message を使う |
cwd |
フックが呼ばれたときの作業ディレクトリ |
scratchpad_dir |
セッションのスクラッチパッドのディレクトリ(Claude が一時的な作業ファイルを置く場所)のパス。セッションにスクラッチパッドが無いか、一時ディレクトリが使えないときは無い。v2.1.257 以降 |
permission_mode |
現在の権限モード:"default"・"plan"・"acceptEdits"・"auto"・"dontAsk"・"bypassPermissions"。Manual と表示されるモードは、"manual" ではなく "default" で届く。このフィールドを受け取らないイベントもあるので、各イベントの節の JSON の例を見る |
effort |
フックが動くときに有効な effort を level フィールドに持つオブジェクト:"low"・"medium"・"high"・"xhigh"・"max"。有効なモデルが対応しない水準を設定していると、level は Claude Code が実際に使った水準を報告する。ステータスラインの effort フィールドと同じオブジェクト。PreToolUse・PostToolUse・Stop・SubagentStop のようにツール使用の文脈で発火するイベントで、現在のモデルが effort のパラメータに対応しているときに存在する。この水準は、フックのコマンドと Bash ツールに環境変数 $CLAUDE_EFFORT としても渡る |
hook_event_name |
発火したイベントの名前 |
--agent で実行しているとき、またはサブエージェントの中では、さらに2つのフィールドが入ります。
| フィールド | 内容 |
|---|---|
agent_id |
サブエージェントの一意の識別子。フックがサブエージェントの呼び出しの中で発火したときだけ存在する。サブエージェントの呼び出しとメインスレッドの呼び出しを区別するのに使う |
agent_type |
エージェント名("Explore"・"security-reviewer" など)。セッションが --agent を使うとき、またはフックがサブエージェントの中で発火するときに存在する。サブエージェントでは、サブエージェントの種類が、セッションの --agent の値より優先される。カスタムとプラグインのサブエージェントが報告する値と、プラグインのスコープ付きの名前に対する matcher の書き方は、SubagentStart の節を参照 |
modelフィールドを受け取れるのはSessionStartのフックだけで、Claude Code が必ず入れるとは限りません。PreModelSwitchとPostModelSwitchのフックは、代わりにfrom_modelとto_modelを受け取るので、セッション中のモデルの変化を追うにはPostModelSwitchのフックを使います$CLAUDE_MODELという環境変数はありません。シェルで設定していれば、フックは$ANTHROPIC_MODELを読めますが、セッション中に/modelでモデルを切り替えても、その値は変わりません- フックのプロセスは、親の環境を引き継ぎます。ただし、Claude Code が起動するすべてのサブプロセスから取り除く
OTEL_*のエクスポーター変数と、CLAUDE_CODE_SUBPROCESS_ENV_SCRUBを1にしたときに取り除かれる変数は除きます
Bash コマンドの PreToolUse フックが stdin で受け取る例:
{
"session_id": "abc123",
"prompt_id": "550e8400-e29b-41d4-a716-446655440000",
"transcript_path": "/home/user/.claude/projects/.../transcript.jsonl",
"cwd": "/home/user/my-project",
"scratchpad_dir": "/tmp/claude-1000/-home-user-my-project/abc123/scratchpad",
"permission_mode": "default",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "npm test",
"description": "Run test suite",
"timeout": 120000,
"run_in_background": false
},
"tool_use_id": "toolu_01ABC123..."
}
tool_name・tool_input・tool_use_id はイベント固有のフィールドです。
終了コードの出力#
フックのコマンドの終了コードは、操作を進めるか、ブロックするか、無視するかを Claude Code に伝えます。終了コードだけで決まるのではありません。Claude Code は、終了コード 0 だけでなくどの終了コードでも stdout から JSON の出力フィールドを読み、標準の決定モデルを使うイベントでは、スキーマ検証を通る解析済みのオブジェクトが、終了コードと並んで有効になります。終了コード 2 のブロックは、JSON が上書きできない唯一の結果です。
イベントごとの例外は、2つの表が持ちます。「イベントごとの終了コード 2 の挙動」は、各イベントで終了コードが何をするかを、「決定の制御」は、各イベントがどの決定フィールドを受け付けるかを示します。systemMessage のようなユニバーサルなフィールドは、ほとんどのイベントで働き、「JSON 出力」の表にあります。
終了コード 0#
終了コード 0 は成功で、構造化された制御のために JSON を出力するときに意図する終了コードです。
- ほとんどのイベントでは、Claude Code は stdout をデバッグログに書き、記録には出しません。例外は
UserPromptSubmit・UserPromptExpansion・SessionStart・PostModelSwitchで、プレーンテキストの stdout が、Claude が見て反応できる文脈として加わります - stdout を JSON 出力として読むかプレーンテキストとして読むかは、前後の空白を無視した始まりと終わりで決まります
{で始まり}で終わる:JSON として解析される。出力が2行以上で、各行がそれだけで JSON として解析でき、どの行も、フィールドを設定する JSON 出力のオブジェクトでないときは、出力全体がプレーンテキストとして扱われる。それらの行の1つでもフィールドを設定していると、出力全体が解析の失敗になる(後述){で始まるが}で終わらない:プレーンテキストとして扱われる- それ以外で始まる:JSON の配列や引用符で囲んだ JSON の文字列を含め、プレーンテキストとして扱われる
- 標準の決定モデルを使うイベントでは、終了コード 0 で、スキーマ検証に失敗した解析済みのオブジェクトは、ブロックしないエラーです。操作は進み、記録に、検証のメッセージつきの
<hook name> hook errorの通知が出ます。2 以外のどの終了コードでも同じで、終了コード 2 はそれでもブロックします - 標準の決定モデルを使うイベントで、Claude Code が stdout を JSON として解析しようとして失敗すると、2 以外のどの終了コードでも、ブロックしないエラーを報告します。記録に、解析のメッセージつきの
<hook name> hook errorの通知が出ます。プレーンテキストの stdout を文脈として加えるイベントでは、Claude Code はそのテキストを加えません。v2.1.248 より前は、その stdout をプレーンテキストとして扱っていました - 終了コード 0 で終わるフックの stderr は、デバッグログにだけ行き、記録には決して出ず、Claude も見ません。自分で読むには、デバッグログを有効にします。
PostToolUseやPostToolUseFailureのフックから Claude に警告を出すには、ツールがすでに動いていても Claude が stderr を見られる終了コード 2 を使います
終了コード 2#
終了コード 2 は、ブロックするエラーです。ブロックできるイベントでは、JSON を出力してもしなくても、終了コード 2 でブロックされます。JSON の permissionDecision: "allow" でも上書きできません。Claude Code は、stdout の有効な JSON の出力を、それでも読みます。Elicitation と ElicitationResult では、終了コード 2 のフックの hookSpecificOutput は無視されます。
- ブロックのメッセージは、JSON がブロックの判定をしていればその理由、そうでなければ stderr のテキストです。ブロックが何をするかはイベントで変わります:
PreToolUseはツール呼び出しをブロックし、UserPromptSubmitはプロンプトを拒否する、など - JSON 出力のスキーマ検証に失敗する JSON を出力しながら終了コード 2 で終わるフックも、ブロックします。Claude Code は stderr をブロックの理由に使い、検証の失敗をデバッグログに記録します。v2.1.214 より前は、この組み合わせはブロックしないエラーとして扱われ、操作は進みました
rm コマンドを終了コード 2 でブロックし、ほかのコマンドは通常の権限の流れに任せるスクリプトの例:
#!/bin/bash
# Reads JSON input from stdin, checks the command
input=$(cat)
command=$(jq -r '.tool_input.command' <<<"$input")
if [[ "$command" == rm* ]]; then
echo "Blocked: rm commands are not allowed" >&2
exit 2 # Blocking error: tool call is prevented
fi
exit 0 # No decision: the normal permission flow applies
それ以外の終了コード#
ほとんどのフックイベントでは、それ以外の終了コードはそれだけではブロックしません。何が起きるかは stdout で決まります。
- スキーマ検証を通る解析済みのオブジェクトがあり、標準の決定モデルを使うイベント:Claude Code は終了コードを無視し、JSON だけが結果を決めます。イベントが対応する各フィールド(
permissionDecision・additionalContext・updatedInput・systemMessageを含む)が有効になり、フックはエラーとして報告されません - スキーマ検証に失敗する解析済みのオブジェクトがあり、標準の決定モデルを使うイベント:終了コード 0 のときと同じ、ブロックしないエラーです。操作は進み、
<hook name> hook errorの通知が検証のメッセージを伝えます - Claude Code が JSON として解析しようとして失敗する stdout がある、標準の決定モデルを使うイベント:終了コード 0 のときと同じ、ブロックしないエラーを報告します。操作は進み、通知が解析のメッセージを伝えます
- プレーンテキストとして扱われる stdout、または空の stdout:ほとんどのフックイベントで、ブロックしないエラーです。操作は進み、記録に、
<hook name> hook errorの通知と、Failed with non-blocking status code:を前に付けた stderr の最初の1行が出ます。stderr の全文を得るには、デバッグログを有効にします - 標準の決定モデルの外のイベントは、「イベントごとの表」に自分の行を持ちます。
WorktreeCreateは、JSON が何を言っていても、どの非 0 でも作成が失敗します。StopFailureのように、フックの出力を完全に捨てるイベントは、どの終了コードでも JSON を無視します(terminalSequenceのような副作用のフィールドは、それでも働きます) - 起動できないフックも、同じブロックしないエラーに入ります。スクリプトのパスが存在しない・実行できないときは、シェルが 127 のような終了コードで終わり、インタプリタのメッセージつきの同じ通知が出ます(例:
Failed with non-blocking status code: /bin/sh: /path/to/hook.sh: No such file or directory)。ほとんどのフックイベントで、操作は進みます。方針のフックを設定するときは、最初の実行でこの通知を見張ってください。settings.jsonのパスの打ち間違いは、ゲートを黙って無効にします
注意
ほとんどのフックイベントでは、終了コードだけでブロックできるのは終了コード 2 だけです。stdout に有効な JSON が無いと、Claude Code は、終了コード 1 が Unix の慣例的な失敗のコードであっても、ブロックしないエラーとして扱い、操作を進めます。方針を強制するフックには exit 2 を使います。worktree のイベントは違います:WorktreeCreate の非 0 の終了コードは worktree の作成を中止し、WorktreeRemove の非 0 の終了コードは、その後もディレクトリが残っていれば worktree の削除を失敗させます。
タイムアウト#
async: true で動かすコマンドフックを除き、Claude Code は、timeout に達した command・http・mcp_tool のフックを取り消し、その出力を捨てるので、ほとんどのイベントで、タイムアウトしたフックは判定を出しません。
PreModelSwitch:タイムアウトで取り消されたフックは、モデルの切り替えをブロックしますPreToolUse:2種類のフックで違います- タイムアウトした
command・http・mcp_toolのフックは、ツール呼び出しをブロックしません。呼び出しは通常の権限の流れを進むので、止まったフックがゲートとして働くことを当てにしないでください - タイムアウトを超えた Agent SDK のコールバックフックは、ツール呼び出しをブロックします
- タイムアウトした
イベントごとの終了コード 2 の挙動#
終了コード 2 は、フックが「止めて、これをしないで」と伝える方法です。効果はイベントで変わります。まだ起きていないツール呼び出しのようにブロックできる動作を表すイベントと、すでに起きたことや防げないことを表すイベントがあるためです。
| フックイベント | ブロックできるか | 終了コード 2 で起きること |
|---|---|---|
PreToolUse |
はい | ツール呼び出しをブロックする |
PermissionRequest |
いいえ | このイベントでは終了コード 2 は有効にならず、権限の流れはそのまま進む。代わりに decision オブジェクトで拒否する |
UserPromptSubmit |
はい | プロンプトをブロックし、Claude に届かないようにする |
UserPromptExpansion |
はい | 展開をブロックする |
Stop |
はい | Claude が止まるのを防ぎ、会話を続ける |
SubagentStop |
はい | サブエージェントが止まるのを防ぐ |
TeammateIdle |
はい | チームメイトがアイドルになるのを防ぎ、作業を続けさせる |
TaskCreated |
はい | タスクの作成を取り消す |
TaskCompleted |
はい | タスクが完了としてマークされるのを防ぐ |
ConfigChange |
はい | 設定の変更が有効になるのをブロックする(policy_settings を除く) |
StopFailure |
いいえ | 出力と終了コードは無視される(terminalSequence を除く) |
PostToolUse |
いいえ | stderr を Claude に見せる。ツールはすでに動いている |
PostToolUseFailure |
いいえ | stderr を Claude に見せる。ツールはすでに失敗している |
PostToolBatch |
はい | 次のモデル呼び出しの前に、エージェントのループを止める |
PermissionDenied |
いいえ | 拒否はすでに起きているので、終了コードと stderr は無視される。モデルが再試行してよいと伝えるには JSON の hookSpecificOutput.retry: true を使う。分類器の判定が無い拒否では retry: true は無視される |
Notification |
いいえ | 終了コードと stderr は無視される |
SubagentStart |
いいえ | stderr をユーザーにだけ見せる |
SessionStart |
いいえ | stderr をユーザーにだけ見せる |
Setup |
いいえ | 終了コードと stderr は無視される |
SessionEnd |
いいえ | stderr をユーザーにだけ見せる |
CwdChanged |
いいえ | stderr をユーザーにだけ見せる |
DirectoryAdded |
いいえ | stderr はデバッグログへ行く。ディレクトリはすでに追加されている |
FileChanged |
いいえ | stderr をユーザーにだけ見せる |
PreCompact |
はい | コンパクションをブロックする |
PostCompact |
いいえ | stderr をユーザーにだけ見せる |
PreModelSwitch |
はい | モデルの切り替えをブロックし、stderr をユーザーに見せる |
PostModelSwitch |
いいえ | stderr をユーザーにだけ見せる。モデルはすでに切り替わっている |
Elicitation |
はい | elicitation を拒否する |
ElicitationResult |
はい | 応答をブロックする(アクションが decline になる) |
WorktreeCreate |
はい | 非 0 の終了コードは worktree の作成を失敗させる |
WorktreeRemove |
はい | 非 0 の終了コードは、その後もディレクトリが残っていれば worktree の削除を失敗させる |
InstructionsLoaded |
いいえ | 終了コードは無視される |
MessageDisplay |
いいえ | 元のテキストが表示される |
SessionStart・SubagentStart・PostModelSwitch では、Claude Code は、終了コード 2 の stderr を、ブロックしないエラーと同じ <hook name> hook error の通知として記録に出します。Claude には見えず、セッションやサブエージェントは進みます。SubagentStart では、通知は親の会話ではなくサブエージェント自身の記録に出ます。
HTTP のレスポンスの扱い#
HTTP フックは、終了コードと stdout の代わりに、HTTP のステータスコードとレスポンスボディを使います。次の結果は、ほとんどのイベントに当てはまります。WorktreeCreate のように、「イベントごとの表」に自分の失敗の取り決めを持つイベントは、失敗した HTTP フックにもその取り決めを適用します。
- 2xx で本文が空:成功。出力なしの終了コード 0 と同じ
- 2xx で JSON オブジェクトの本文:コマンドフックと同じ JSON 出力のスキーマで解析される。スキーマ検証に失敗する本文は、ブロックしないエラー
- 2xx でそれ以外の本文(プレーンテキストなど):ブロックしないエラー。2xx 以外のステータスと同じ扱いで、Claude Code はそのテキストを Claude の文脈に加えない
- 2xx 以外のステータス:ブロックしないエラー。実行は続く
- 接続の失敗:ブロックしないエラー。実行は続く
- タイムアウト:フックは取り消される(前述「タイムアウト」)
コマンドフックと違い、HTTP フックは、ステータスコードだけでブロックするエラーを知らせられません。ツール呼び出しをブロックしたり権限を拒否したりするには、適切な決定のフィールドを持つ JSON の本文つきの 2xx のレスポンスを返します。
JSON 出力#
終了コードでできるのはブロックするか黙るかだけですが、JSON 出力ならより細かく制御できます。終了コード 2 でブロックする代わりに、終了コード 0 で JSON オブジェクトを stdout に出します。Claude Code は、その JSON の特定のフィールドを読んで動作を制御します。ブロック・許可・ユーザーへの引き上げのための決定の制御を含みます。
補足
フックごとに、終了コードだけで知らせるか、終了コード 0 と JSON で構造化された制御をするか、どちらかを選びます。混ぜると、終了コード 2 はブロックの効果を保ち、Claude Code は JSON のフィールドも読みます(終了コード 2 の節に書いた elicitation の例外を除く)。
- フックの stdout は、JSON オブジェクトだけにします。シェルのプロファイルが起動時にテキストを出すと、JSON の解析を妨げます(フックの使い方のトラブルシューティングを参照)
- フックの
additionalContext・systemMessage・initialUserMessageの文字列と、プレーンな stdout は、10,000 文字が上限です- 範囲:同じイベントで複数のフックが動いても、Claude Code は文字列ごとに別々に測る。JSON 出力ではフィールドごと、プレーンな stdout は全体で測る
- 上限を超えたとき:Claude Code は、出力をセッションディレクトリのファイルに保存し、ファイルのパスと先頭 2,000 文字までのプレビューに置き換える。大きな有効な Bash の結果も、同じように扱われる(ツールの出力制限を参照)。その Bash の上限と違い、この上限には、上げる設定も環境変数もない
- ファイルの読み取り:Claude Code は、Claude にそのファイルを読むよう求めないので、Claude が必ず見るべきものは、上限の中に収める
JSON オブジェクトは3種類のフィールドに対応します。
- ユニバーサルなフィールド:下の表の
continueなど。どのイベントも受け付けるが、イベントによっては捨てたり、systemMessageを記録以外の場所へ届けたりする。各イベントの節に書いてある。terminalSequenceも、「ターミナル通知を出す」に挙げた例外を除き、これらのイベントで働く - 最上位の
decisionとreason:一部のイベントが、ブロックやフィードバックのために使う hookSpecificOutput:より豊かな制御が要るイベントのための入れ子のオブジェクト。イベント名を設定したhookEventNameフィールドが必須
| フィールド | 既定 | 内容 |
|---|---|---|
continue |
true |
false なら、フックが動いた後、Claude が処理を完全に止める。イベント固有の決定フィールドより優先される |
stopReason |
なし | continue が false のときにユーザーへ出すメッセージ。会話に残るので、会話が続くなら Claude も見る |
suppressOutput |
false |
効果なし:Claude Code はこのフィールドを受け付けるが、何もしない。成功したフックの stdout は記録には決して出ず、デバッグログに記録される |
systemMessage |
なし | ユーザーへ出す警告メッセージ。Agent SDK と --output-format stream-json の出力では、SDKInformationalMessage として届くことがある |
terminalSequence |
なし | デスクトップ通知・ウィンドウタイトル・ベルなど、Claude Code があなたに代わって出すターミナルのエスケープシーケンス。OSC 0/1/2/9/99/777 と BEL に限る。許可リストの外のものが含まれていると、フィールドは無視される。フックには使えない /dev/tty への書き込みの代わりに使う |
Claude を完全に止めるには:
{ "continue": false, "stopReason": "Build failed, fix errors before continuing" }
PreToolUse と PostToolUse のフックでは、ツール呼び出しが失敗したときや、Claude がまだ応答をストリームしている間に完了したときにも、停止が適用されます。
ターミナル通知を出す#
フックは制御端末なしで動くので、エスケープシーケンスを /dev/tty に直接書くのは失敗します。代わりに、エスケープシーケンスを terminalSequence フィールドに入れて返すと、Claude Code が自分のターミナル書き込みの経路で出してくれます。競合が起きず、tmux と GNU screen の中でも、/dev/tty が無い Windows でも動きます。このフィールドは、許可リストに入った1つ以上のエスケープシーケンスの文字列を受け付けます。
- OSC
0・1・2:ウィンドウとアイコンのタイトル - OSC
9:iTerm2・ConEmu・Windows Terminal・WezTerm の通知(9;4のタスクバーの進捗を含む) - OSC
99:Kitty の通知 - OSC
777:urxvt・Ghostty・Warp の通知 - 単独の BEL
シーケンスは BEL か ST で終えられます。CSI のカーソルや色のシーケンス・OSC のパレットのシーケンス・OSC 8 のハイパーリンク・OSC 52 のクリップボード書き込み・OSC 1337 を含む、許可リストの外のものは拒否され、フィールドが無視されます。
- Claude Code は、フックの出力を処理するときにシーケンスを自分で書くので、このフィールドは、
NotificationやStopFailureのようにsystemMessageとcontinueを捨てるイベントでも働きます。2つの制限があります- Claude Code がシーケンスを書くのは、対話セッションで、画面が出ている間だけ。
-pフラグの非対話モードと Agent SDK では、フィールドを無視する WorktreeCreateのコマンドフックは、Claude Code が stdout を worktree のパスとして読むので、JSON を返せない。HTTP のWorktreeCreateフックは JSON を返せて、このフィールドを含められる
- Claude Code がシーケンスを書くのは、対話セッションで、画面が出ている間だけ。
Notification フックからデスクトップ通知を出す例です。エスケープシーケンスは、制御用のバイトがシェルのコマンドラインに現れないよう printf の8進のエスケープで作り、jq -n --arg で JSON の出力を作って、通知メッセージ中の引用符・バックスラッシュ・改行が正しくエスケープされるようにしています。
#!/bin/bash
# Notification hook: ping the desktop when Claude Code needs attention.
input=$(cat)
title="Claude Code"
body=$(jq -r '.message // "Needs your attention"' <<<"$input")
seq=$(printf '\033]777;notify;%s;%s\007' "$title" "$body")
jq -nc --arg seq "$seq" '{terminalSequence: $seq}'
{ "terminalSequence": "..." } の形は、どのシェルや言語からでも同じです。
Claude に文脈を渡す#
additionalContext フィールドは、フックの文字列を Claude のコンテキストウィンドウに渡します。Claude Code は、その文字列をシステムリマインダーで包み、フックが発火した時点の会話に挿入します。Claude は次のモデルリクエストでそのリマインダーを読みますが、画面にはチャットのメッセージとして出ません。additionalContext は、イベント名と並べて hookSpecificOutput の中に返します。
{
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": "This file is generated. Edit src/schema.ts and run `bun generate` instead."
}
}
リマインダーが現れる場所は、イベントで違います。
-
SessionStartとSubagentStart:会話の始まり、最初のプロンプトの前 -
UserPromptSubmitとUserPromptExpansion:送信されたプロンプトと並んで -
PreToolUse・PostToolUse・PostToolUseFailure・PostToolBatch:ツールの結果の隣 -
StopとSubagentStop:ターンの終わり。Claude がフィードバックに反応できるよう、会話が続く -
PostModelSwitch:切り替え後の次のリクエストに付く -
同じイベントで複数のフックが
additionalContextを返すと、Claude はすべての値を受け取ります -
値が 10,000 文字を超えると、Claude Code はテキストをセッションディレクトリのファイルに書き、ファイルのパスと先頭 2,000 文字までのプレビューを Claude に渡します。Claude はそのファイルを読めますが、Claude Code は読むよう求めません
-
additionalContextは、Claude が、いまの環境の状態や、いま実行した操作について知っておくべき情報に使います:環境の状態(現在のブランチ・デプロイ先・有効な機能フラグ)/条件つきのプロジェクトのルール(いま編集したファイルにどのテストコマンドが当てはまるか・この worktree でどのディレクトリが読み取り専用か)/外部のデータ(自分に割り当てられた open の issue・最近の CI の結果・社内サービスから取った内容) -
変わらない指示には、CLAUDE.md を使います。スクリプトを動かさずに読み込まれる、静的なプロジェクトの規約の標準の置き場所です
-
テキストは、命令調のシステム指示でなく、事実の文として書きます。「The deployment target is production」や「This repo uses
bun test」のような言い方は、プロジェクトの情報として読まれます。帯域外のシステムコマンドのように見える書き方は、Claude のプロンプトインジェクションへの防御を起こし、Claude が文脈として扱わず、そのテキストをあなたに見せてしまうことがあります -
Claude Code は、注入したテキストをセッションの記録に保存します。
PostToolUseやUserPromptSubmitのようなセッション途中のイベントでは、--continueや--resumeで再開したとき、過去のターンのフックを再実行せず保存したテキストを再生するので、タイムスタンプやコミット SHA のような値は古くなります。SessionStartのフックは、再開時にsourceを"resume"(--fork-sessionを付けたなら"fork")にして再び動くので、文脈を更新できます
決定の制御#
JSON でブロックや動作の制御に対応するイベントは一部だけで、対応するイベントは、それぞれ違うフィールドで決定を表します。フックを書く前の早見表です。
| イベント | 決定のパターン | 主なフィールド |
|---|---|---|
| UserPromptSubmit・UserPromptExpansion・PostToolUse・PostToolUseFailure・PostToolBatch・Stop・SubagentStop・ConfigChange・PreCompact | 最上位の decision |
decision: "block"・reason。Stop と SubagentStop は、会話を続ける、エラーでないフィードバックのために hookSpecificOutput.additionalContext も受け付ける |
| TeammateIdle・TaskCompleted | 終了コードか continue: false |
終了コード 2 が、stderr のフィードバックつきで操作をブロックする。JSON の {"continue": false, "stopReason": "..."} も、Stop フックと同じように、チームメイトを完全に止める。TaskUpdate ツールがイベントを起こしたときは、TaskCompleted はこれを無視する |
| TaskCreated | 終了コードか最上位の decision |
終了コード 2 か decision: "block" がタスクを取り消し、メッセージを Claude に返す。continue: false は無視される |
| PreToolUse | hookSpecificOutput |
permissionDecision(allow/deny/ask/defer)・permissionDecisionReason |
| PreModelSwitch | hookSpecificOutput か最上位の decision |
permissionDecision(allow/deny/ask)・permissionDecisionReason。decision: "block" も切り替えを取り消す |
| PermissionRequest | hookSpecificOutput |
decision.behavior(allow/deny) |
| PermissionDenied | hookSpecificOutput |
retry: true が、拒否されたツール呼び出しをモデルが再試行してよいと伝える。分類器の判定が無い拒否では無視される |
| WorktreeCreate | パスを返す | コマンドフックは stdout にパスを出し、HTTP フックは hookSpecificOutput.worktreePath を返す。フックの失敗かパスが無いと作成が失敗する |
| WorktreeRemove | 終了コード | 非 0 の終了コードは、その後もディレクトリが残っていれば削除を失敗させる。JSON 出力は捨てられる |
| Elicitation | hookSpecificOutput |
action(accept/decline/cancel)・content(accept のときのフォームのフィールド値) |
| ElicitationResult | hookSpecificOutput |
action(accept/decline/cancel)・content(フォームのフィールド値の上書き) |
| MessageDisplay | hookSpecificOutput |
displayContent が画面に表示するテキストを置き換える。表示だけで、記録と Claude が見るものは元のまま |
| SessionStart・SubagentStart・PostModelSwitch | 文脈のみ | hookSpecificOutput.additionalContext が Claude に文脈を加える。SessionStart は initialUserMessage・watchPaths・sessionTitle・reloadSkills も受け付ける。ブロックや決定の制御は無い |
| Setup・Notification・SessionEnd・PostCompact・InstructionsLoaded・StopFailure・CwdChanged・DirectoryAdded・FileChanged | なし | 決定の制御は無い。ログや後始末のような副作用のために使う |
一部のイベントは、許可やブロックだけでなく、内容を書き換えることもできます。
PreToolUse:hookSpecificOutput直下のupdatedInputが、ツールの引数を実行前に置き換えるPermissionRequest:decisionオブジェクトの中のupdatedInputPostToolUse:updatedToolOutputがツールの結果を置き換えるUserPromptSubmit:プロンプトを置き換えられない。並べてadditionalContextを注入できるだけ
伏せ字や変換の用途では、出ていく入力は PreToolUse で、入ってくるツールの結果は PostToolUse で捕まえます。
各パターンの例:
最上位の decision:decision の値は "block" だけです。操作を進めるには、JSON から decision を省くか、JSON なしで終了コード 0 で終えます。
{
"decision": "block",
"reason": "Test suite must pass before proceeding"
}
PreToolUse:hookSpecificOutput で、許可・拒否・ユーザーへの引き上げができます。実行前にツールの入力を変えたり、Claude に追加の文脈を注入したりもできます。
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Database writes are not allowed"
}
}
PermissionRequest:hookSpecificOutput で、ユーザーに代わって権限リクエストを許可・拒否します。許可するときは、ツールの入力を変えたり、ユーザーに再度確認しないよう権限ルールを適用したりもできます。
{
"hookSpecificOutput": {
"hookEventName": "PermissionRequest",
"decision": {
"behavior": "allow",
"updatedInput": {
"command": "npm run lint"
}
}
}
}
Bash コマンドの検証・プロンプトのフィルタリング・自動承認のスクリプトを含む詳しい例は、フックの使い方にあります。
フックイベント#
各イベントは、フックが動ける Claude Code のライフサイクルの時点に対応します。以降の節は、セッションの準備から、エージェントのループを経て、セッションの終了まで、ライフサイクルの順に並んでいます。各節は、イベントがいつ発火するか・どの matcher に対応するか・受け取る JSON の入力・出力で動作を制御する方法を説明します。
SessionStart#
新しいセッションの開始時と、既存のセッションの再開時に動きます。既存の issue や最近の変更のような開発の文脈を読み込んだり、環境変数を設定したりするのに向きます。スクリプトが要らない静的な文脈なら、CLAUDE.md を使います。
- SessionStart はすべてのセッションで動くので、フックは速くしておきます。対応するのは
type: "command"とtype: "mcp_tool"のフックだけです(mcp_toolが動く条件は、前述の MCP ツールフックのフィールドの節) - 対話セッションの開始時、
--continueか--resumeでの起動時の再開、/clearでは、SessionStart のフックはバックグラウンドで動きます。すぐに入力でき、再開した会話はフックを待たずに表示されます。ただし Claude の最初の応答は、フックの文脈が Claude に届くよう、フックが終わるのを待ちます - セッション内で
/resumeで会話を切り替えるときは、代わりに切り替えがフックの終了を待ちます。バックグラウンドのフックが動いている間に/clearを実行したり別の会話に切り替えたりすると、そのフックが返したものは、セッションに適用されません - 起動時(再開したセッションを含む)にも同じ待ちが適用され、SessionStart のフックが動いている間に送ったプロンプトは、フックが終わるまで Claude に届きません。どちらの待ちの間も、Esc でプロンプトを送らず入力欄に戻せます(フックは動き続けます)
matcher の値は、セッションの始まり方に対応します。
| matcher | 発火するとき |
|---|---|
startup |
新しいセッション |
resume |
--resume・--continue・/resume |
clear |
/clear |
compact |
自動または手動のコンパクション |
fork |
既存のセッションからフォークした新しいセッション:--resume か --continue に --fork-session、/fork のバックグラウンドコピー、/branch、バックグラウンドへ移した会話 |
v2.1.214 より前は、フォークされたセッションの source は "resume" でした。
SessionStart の入力#
共通の入力フィールドに加えて、source と、任意の model・agent_type・session_title を受け取ります。
| フィールド | 内容 |
|---|---|
source |
セッションの始まり方:新規は "startup"、再開は "resume"、/clear の後は "clear"、コンパクションの後は "compact"、既存からフォークした新しいセッションは "fork" |
model |
有効なモデルの識別子。/clear の後や、会話の復旧でセッションが復元されたときなど、省かれることがあるので、読む前にフィールドがあるか確かめる |
agent_type |
エージェント名。claude --agent <name> で起動したときに存在する |
session_title |
セッションのカスタムタイトル。--name・/rename・フックの sessionTitle 出力・Agent SDK の renameSession() などで設定したときに存在する。sessionTitle を出すフックは、先にこのフィールドを確かめて、既存のカスタムタイトルの上書きを避けられる |
名前を付けていないセッションにも、生成されたタイトルがあることがありますが、それはカスタムタイトルではなく、session_title には出ません。
source が "resume" か "fork" で、記録に Claude の応答が1つ以上あるとき、SessionStart のフックは次の4つのフィールドも受け取ります。最初のリクエストの前に、古い会話の再開にかかるコストを(systemMessage などで)報告するのに使えます。v2.1.251 以降が必要です。
| フィールド | 内容 |
|---|---|
seconds_since_last_response |
再開した記録の最後の応答からの経過秒数(実時間) |
context_tokens |
再開したセッションの最初のリクエストが、プロンプトとして再送するトークン数 |
prompt_cache_likely_expired |
最後の応答がセッションのプロンプトキャッシュの有効期間より古いか、後のコンパクションがキャッシュ済みの会話を置き換えたとき true |
estimated_cache_write_usd |
セッションのモデルで context_tokens をプロンプトキャッシュに書き込む推定コスト(米ドル。応答を除く) |
最後の応答から 90 分後に再開したセッションの入力の例:
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "SessionStart",
"source": "resume",
"model": "claude-opus-5",
"seconds_since_last_response": 5400,
"context_tokens": 182340,
"prompt_cache_likely_expired": true,
"estimated_cache_write_usd": 1.1396
}
SessionStart の決定の制御#
Claude Code は、プレーンテキストとして扱う stdout を Claude の文脈に加えます。全フックが使える JSON 出力のフィールドに加えて、次のイベント固有のフィールドを返せます。
| フィールド | 内容 |
|---|---|
additionalContext |
会話の始まり、最初のプロンプトの前に、Claude の文脈へ加わる文字列 |
initialUserMessage |
セッションの最初のユーザーメッセージに使われる文字列。-p フラグの非対話モードで有効で、プロンプトが渡されなくても最初のターンになる。プロンプトが渡されていれば、次のターンとして続く。既存のターンに付く additionalContext と違い、ターンを作る |
sessionTitle |
セッションのタイトルを設定する(/rename と同じ効果)。起動したフォルダ・git のブランチ・worktree の名前から、セッションに自動で名前を付けるのに使う。source が "startup"・"resume"・"fork" のときに有効で、"clear" と "compact" では無視される |
watchPaths |
このセッションの間、FileChanged イベントのために監視する絶対パスの配列 |
reloadSkills |
真偽値。true だと、SessionStart のフックの完了後に、Claude Code がスキルとコマンドのディレクトリを再走査し、フックがインストールしたスキルが、最初のプロンプトから同じセッションで使えるようになる |
{
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": "Current branch: feat/auth-refactor\nUncommitted changes: src/auth.ts, src/login.tsx\nActive issue: #4211 Migrate to OAuth2",
"sessionTitle": "auth-refactor"
}
}
このイベントではプレーンな stdout がすでに Claude に届くので、文脈を読み込むだけのフックは、JSON を作らず stdout に直接出せます。sessionTitle など、ほかのフィールドと組み合わせるときに、JSON の形を使います。
SessionStart のフックがスキルをインストールまたは更新するときは、reloadSkills を使います。スキルの探索は通常、SessionStart のフックが終わる前に済むので、フックが ~/.claude/skills/ や .claude/skills/ に書いたファイルは、そのままでは次のセッションにしか現れません。共有のスキルのリポジトリを同期して、再走査を求める例です。
#!/bin/bash
git -C ~/.claude/skills/team-skills pull --quiet 2>/dev/null || \
git clone --quiet https://git.example.com/your-org/team-skills.git ~/.claude/skills/team-skills
echo '{"hookSpecificOutput": {"hookEventName": "SessionStart", "reloadSkills": true}}'
リポジトリの URL はプレースホルダーなので、自分のスキルのリポジトリに置き換えます。プレースホルダーのままではクローンが失敗し、fatal: のメッセージを stderr に出します。終了コード 0 で終わる SessionStart のフックの stderr は情報用だけなので、reloadSkills の要求は、それでも適用されます。
環境変数を永続化する#
SessionStart のフックは、後続の Bash コマンドのために環境変数を永続化できるファイルパスを示す環境変数 CLAUDE_ENV_FILE を使えます。個別の環境変数を設定するには、export 文を CLAUDE_ENV_FILE に書きます。ほかのフックが設定した変数を残すため、追記(>>)を使います。
#!/bin/bash
if [ -n "$CLAUDE_ENV_FILE" ]; then
echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE"
echo 'export DEBUG_LOG=true' >> "$CLAUDE_ENV_FILE"
echo 'export PATH="$PATH:./node_modules/.bin"' >> "$CLAUDE_ENV_FILE"
fi
exit 0
セットアップのコマンドによる環境の変化をすべて捕まえるには、書き出した変数を前後で比べます。
#!/bin/bash
ENV_BEFORE=$(export -p | sort)
# Run your setup commands that modify the environment
source ~/.nvm/nvm.sh
nvm use 20
if [ -n "$CLAUDE_ENV_FILE" ]; then
ENV_AFTER=$(export -p | sort)
comm -13 <(echo "$ENV_BEFORE") <(echo "$ENV_AFTER") >> "$CLAUDE_ENV_FILE"
fi
exit 0
補足
CLAUDE_ENV_FILE が使えるのは、SessionStart・Setup・CwdChanged・FileChanged のフックだけです。ほかのフックの種類は、この変数を使えません。
Setup#
--init-only で起動したとき、または -p フラグの非対話モードで --init か --maintenance を付けて起動したときだけ発火します。通常の起動では発火しません。CI やスクリプトから、通常のセッション開始とは別に明示的に起こす、1回限りの依存関係のインストールや定期的な後始末に使います。セッションごとの初期化には、代わりに SessionStart を使います。
| matcher | 発火するとき |
|---|---|
init |
claude --init-only か claude -p --init |
maintenance |
claude -p --maintenance |
claude --init-onlyを実行すると、Claude Code は Setup のフックと、startupの matcher のSessionStartのフックを動かし、会話を始めずに終了します-pで会話を始めるか続けるときは、引数か stdin のパイプでプロンプトを渡す必要もあります。SessionStartのフックがinitialUserMessageを供給するときか、保留したツール呼び出しでセッションを再開するときは、プロンプトを省けます- 成功すると、
--init-onlyはターミナルに何も出しません。フックが動いたかを確かめるには、claude --debug-file <path> --init-only(<path>はログファイルの場所)で起動し、ログの Setup と SessionStart のフックの項目を見ます - Setup は起動のたびには発火しないので、依存関係のインストールが要るプラグインは、Setup だけには頼れません。実用的な形は、初回の使用時に依存関係を確かめ、無ければインストールすることです(
${CLAUDE_PLUGIN_DATA}/node_modulesを調べ、無ければnpm installを動かすフックやスキルなど)。インストールした依存関係の置き場は、プラグインの永続データディレクトリを参照してください。マーケットプレイスでプラグインを配るなら、この形は要らないかもしれません:Claude Code は、プラグインをキャッシュするとき、対象の Node.js パッケージの依存関係を自動でインストールします
Setup の入力#
共通の入力フィールドに加えて、"init" か "maintenance" を設定した trigger フィールドを受け取ります。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "Setup",
"trigger": "init"
}
Setup の決定の制御#
Setup のフックはブロックできず、どの終了コードでも実行は続きます。Claude Code は、どの終了コードでも、Setup のフックの JSON 出力のフィールド(systemMessage・continue・hookSpecificOutput.additionalContext など)を捨てます。-p では、--output-format stream-json --verbose で起動したときだけ、Setup のフックの stdout・stderr・終了コードが hook_response イベントとして実行の出力に現れます。
Setup のフックは CLAUDE_ENV_FILE を使えます。そのファイルに書いた変数は、SessionStart のフックと同じく、セッションの後続の Bash コマンドに残ります。Setup で動くのは type: "command" のフックだけで、Setup の type: "mcp_tool" のフックは常に飛ばされます。
InstructionsLoaded#
CLAUDE.md や .claude/rules/*.md が文脈に読み込まれたときに発火します。セッション開始時に先に読み込まれたファイルについて発火し、後からファイルが遅延読み込みされたとき(Claude が入れ子の CLAUDE.md のあるサブディレクトリにアクセスしたとき、paths: frontmatter のある条件つきルールが一致したときなど)にも発火します。ブロックや決定の制御には対応せず、観測のために非同期で動きます。
- このイベントは、Project instructions 設定で Claude が
AGENTS.mdを直接読むときには発火しません。CLAUDE.mdがAGENTS.mdをインポートするときは、ほかのインポートファイルと同じくload_reasonがincludeで発火し、CLAUDE.mdがAGENTS.mdへのシンボリックリンクのときは、通常のCLAUDE.mdの読み込みとして発火します - matcher は
load_reasonに対して評価されます。セッション開始時に読み込まれたファイルだけで発火するには"matcher": "session_start"、遅延読み込みだけなら"matcher": "path_glob_match|nested_traversal"を使います
InstructionsLoaded の入力#
| フィールド | 内容 |
|---|---|
file_path |
読み込まれた指示ファイルの絶対パス |
memory_type |
ファイルの範囲:"User"・"Project"・"Local"・"Managed" |
load_reason |
読み込まれた理由:"session_start"・"nested_traversal"・"path_glob_match"・"include"・"compact"。"compact" は、コンパクションのイベントの後に指示ファイルが読み込み直されたときに発火する |
globs |
ファイルの paths: frontmatter のパスの glob パターン(あれば)。path_glob_match の読み込みにだけ存在する |
trigger_file_path |
遅延読み込みで、この読み込みのきっかけになったアクセス先のファイルのパス |
parent_file_path |
include の読み込みで、これを含めた親の指示ファイルのパス |
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
"cwd": "/Users/my-project",
"hook_event_name": "InstructionsLoaded",
"file_path": "/Users/my-project/CLAUDE.md",
"memory_type": "Project",
"load_reason": "session_start"
}
InstructionsLoaded の決定の制御#
決定の制御はありません。指示の読み込みをブロックしたり変更したりできず、systemMessage や continue などの JSON 出力のフィールドは捨てられます。監査ログ・コンプライアンスの追跡・観測に使います。
UserPromptSubmit#
プロンプトが送信されたとき、Claude が処理する前に動きます。プロンプトや会話に基づいて追加の文脈を足したり、プロンプトを検証したり、特定の種類のプロンプトをブロックしたりできます。
UserPromptSubmit のフックは、自分で打ったプロンプトのときだけ発火するのではありません。Claude Code は次のときにも動かします。
-
定期実行のタスクが発火したとき(
/loopの1回の繰り返しを含む) -
バックグラウンドのサブエージェントが、自分を始めたセッションへ報告を返したとき
-
別のセッションがメッセージをメインの会話へ送ってきたとき
-
command・http・mcp_toolの種類の既定のタイムアウトは 30 秒で、ほかのほとんどのイベントでのそれらの種類の既定の 600 秒より短いです。このフックはすべてのプロンプトの前に動き、完了するまでモデルの処理をブロックするので、止まったフックはセッションを止めます。もっと時間が要るなら、フックのエントリでtimeoutを設定します -
async: trueで動かすコマンドフックを除き、タイムアウトに達したUserPromptSubmitのコマンド・HTTP・MCP ツールのフックは取り消され、additionalContextを含む出力は捨てられます。プロンプトは、その文脈なしで Claude に届きます。記録には、フック名・発火したタイムアウト・出力が捨てられたことを示す通知が出ます -
UserPromptSubmitの Agent SDK のコールバックフックがタイムアウトに達すると、フック名とタイムアウトを示すメッセージつきでプロンプトをブロックします(そこのコールバックは、失敗しても通してはいけない方針のゲートとして働いていることがあるため)。セッションは続きます。v2.1.208 より前は、そのイベントのコールバックのタイムアウトは、実行エラーでターンを終えていました
UserPromptSubmit の入力#
共通の入力フィールドに加えて、送信されたテキストを持つ prompt フィールドを受け取ります。[Pasted text #N] のプレースホルダーに畳まれた貼り付けの内容は、その場で展開されて届きます。Claude Code が貼り付けたテキストを Claude 向けに印を付けるセッションでは、展開された内容が <pasted_content id="…"> の行と </pasted_content id="…"> の行の間に入るので、フックがプロンプトを解析するときはその行を考慮します。セッションにカスタムタイトルがあるときは session_title も受け取ります(SessionStart の session_title と同じ意味)。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "UserPromptSubmit",
"prompt": "Write a function to calculate the factorial of a number"
}
UserPromptSubmit の決定の制御#
送信されたプロンプトを処理するかどうかを制御し、文脈を足せます。JSON 出力のフィールドはすべて使えます。終了コード 0 で会話に文脈を足す方法は2つあります。
- プレーンテキストの stdout:Claude Code が、プレーンテキストとして扱う stdout を Claude の文脈に加える
additionalContextを持つ JSON:下の JSON 形式でより細かく制御する。additionalContextフィールドが文脈として加わる
どちらの経路も、記録に見える項目は作りません。プレーンな stdout と additionalContext の値は、それぞれフック名で始まるシステムリマインダーとして注入され、Claude は両方を読みます。届いたかを確かめるには、デバッグログを見ます。プロンプトをブロックするには、decision を "block" にした JSON オブジェクトを返します。
| フィールド | 内容 |
|---|---|
decision |
"block" で、プロンプトが Claude に届く前に止める。省くとプロンプトは進む |
reason |
decision が "block" のとき、ユーザーに出す。文脈には加わらない |
additionalContext |
送信されたプロンプトと並んで Claude の文脈に加わる文字列 |
sessionTitle |
セッションのタイトルを設定する。プロンプトの内容に基づいて自動で名前を付けるのに使う |
suppressOriginalPrompt |
フックがプロンプトをブロックするとき true なら、ブロックメッセージにプロンプトのテキストを入れない |
終了コード 2 でブロックするフックも reason と同じ経路をたどり、ブロックメッセージが stderr のテキストをユーザーに見せ、文脈には加わりません。
{
"decision": "block",
"reason": "Explanation for decision",
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"additionalContext": "My additional context here",
"sessionTitle": "My session title",
"suppressOriginalPrompt": true
}
}
ブロックしたプロンプトが残すもの#
ブロックされたプロンプトは Claude に届きませんが、そのテキストはあらゆる場所から消えるわけではありません。既定では、ユーザーに出すブロックメッセージが Original prompt: と送信されたテキストで終わり、Claude Code はそのメッセージをディスク上のセッションの記録ファイルに書きます。テキストをメッセージから除くには、hookSpecificOutput の中に "suppressOriginalPrompt": true を入れた JSON を出力します。decision: "block" でブロックしても、終了コード 2 でブロックしても有効です。JSON を出力しない終了コード 2 のフックは、ブロックメッセージに必ずプロンプトのテキストが入ります。
suppressOriginalPrompt が変えるのはブロックメッセージだけです。送信されたテキストは、セッションの記録やプロンプトの履歴などのローカルファイルに残りうるので、ブロックするフックは、秘密をディスクに残さない方法にはなりません。
UserPromptExpansion#
ユーザーが入力したコマンドが、Claude に届く前にプロンプトへ展開されるときに動きます。特定のコマンドの直接の呼び出しをブロックする、特定のスキルに文脈を注入する、ユーザーがどのコマンドを呼んだかを記録する、といった用途に向きます。たとえば、deploy に一致するフックは、承認ファイルが無ければ /deploy をブロックでき、レビュー用のスキルに一致するフックは、チームのレビューのチェックリストを additionalContext として足せます。
- このイベントは、
PreToolUseがカバーしない経路を扱います。Skillツールに一致するPreToolUseのフックは Claude がツールを呼んだときだけ発火し、/skillnameを直接入力するとPreToolUseを迂回します。UserPromptExpansionはその直接の経路で発火します command_nameに一致します。すべてのプロンプト型コマンドで発火させるには、matcher を空にします
UserPromptExpansion の入力#
共通の入力フィールドに加えて、expansion_type・command_name・command_args・command_source・元の prompt 文字列を受け取ります。expansion_type は、スキルとカスタムコマンドなら slash_command、MCP サーバーのプロンプトなら mcp_prompt です。
{
"session_id": "abc123",
"transcript_path": "/Users/.../00893aaf.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "UserPromptExpansion",
"expansion_type": "slash_command",
"command_name": "example-skill",
"command_args": "arg1 arg2",
"command_source": "plugin",
"prompt": "/example-skill arg1 arg2"
}
UserPromptExpansion の決定の制御#
展開をブロックしたり文脈を足したりできます。JSON 出力のフィールドはすべて使えます。
| フィールド | 内容 |
|---|---|
decision |
"block" で、コマンドが展開されるのを防ぐ。省くと進む |
reason |
decision が "block" のとき、ユーザーに出す |
additionalContext |
展開されたプロンプトと並んで Claude の文脈に加わる文字列 |
終了コード 2 でブロックするフックも reason と同じ経路をたどり、ブロックメッセージが stderr のテキストをユーザーに見せます。
{
"decision": "block",
"reason": "This slash command is not available",
"hookSpecificOutput": {
"hookEventName": "UserPromptExpansion",
"additionalContext": "Additional context for this expansion"
}
}
MessageDisplay#
アシスタントのメッセージが画面へストリームされている間に動きます。Claude Code はメッセージを少しずつ表示します。新しく完成した行のまとまりが描画できるたびに、フックがその行を渡されて1回動き、Claude Code はその場所にフックの置換テキストを描画します。長いメッセージでは複数回、短いメッセージでは1回だけ呼ばれることがあります。
- 用途:最小限の表示のために Markdown を取り除く/Agent SDK のアプリがユーザーに見せるテキストを変換する/Claude の応答から API キーや内部のホスト名を伏せ字にする
- Claude Code は、フックが返るまで各まとまりを保留するので、フックは速くします。フックが失敗するかタイムアウトすると、元のテキストが表示されます。このイベントの既定のタイムアウトは 10 秒で、もっと時間が要るなら
timeoutを設定します - 表示専用です。置換テキストが変えるのは画面に描画されるものだけで、記録と Claude が見るものは元のテキストのままなので、Claude は置換を見ません。verbose モードも元のテキストを表示します。フックが受け取るのはアシスタントのメッセージのテキストだけで、ツールの結果と自分が入力したテキストは、そのまま描画されます
- matcher には対応せず、テキストをストリームするすべてのアシスタントのメッセージで発火します(ツール呼び出しだけの応答のように、テキストの無いメッセージでは発火しません)
- 非対話の実行(Agent SDK のクエリと
claude -pを含む)では、MessageDisplay は、行のまとまりごとでなくアシスタントのメッセージごとに1回動きます。その1回の呼び出しは、メッセージの完了後に届き、全文を持ちます:indexは0、finalはtrue、deltaにメッセージ全体が入ります。メッセージごとのdeltaのテキストを集めるフックは、どちらのモードでも同じ合計のテキストを受け取ります
MessageDisplay の入力#
共通の入力フィールドに加えて、ターンとメッセージの識別子・メッセージ内でのこの呼び出しの位置・delta の新しいテキストを受け取ります。まとまりの境界はテキストのストリームのされ方で決まるので、行が特定のグループになることを当てにせず、index と final でメッセージ内の進み具合を追います。
| フィールド | 内容 |
|---|---|
turn_id |
現在のターンの UUID |
message_id |
表示中のアシスタントのメッセージの UUID。同じメッセージのどのまとまりでも変わらない。API の msg_… の ID ではないので、記録のメッセージ ID とは突き合わせられない |
index |
メッセージ内でのこのまとまりの、0 始まりの番号 |
final |
メッセージの最後のまとまりで true。各メッセージに最終のまとまりはちょうど1つある |
delta |
前のまとまりから新しく完成した行(終端の改行を含む)。最終のまとまり以外は常に完全な行で、最終のまとまりは行の途中で終わることがある。対話の実行では、メッセージが改行で終わるとき、最終のまとまりの delta は空になるので、メッセージの終わりの合図には、空でない delta でなく final を使う。Agent SDK と claude -p の実行では、1回の呼び出しがメッセージ全体を持つ |
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
"cwd": "/Users/my-project",
"hook_event_name": "MessageDisplay",
"turn_id": "0c9e6a2f-7d41-4f4e-9a15-3f4f7c2b8d10",
"message_id": "5b2a9c8e-1f63-4d8a-b7c4-9e0d2a6f1c3b",
"index": 0,
"final": false,
"delta": "Here is the plan:\n"
}
MessageDisplay の出力#
全フックが使える JSON 出力のフィールドに加えて、画面上の delta を置き換える displayContent を返せます。
| フィールド | 内容 |
|---|---|
displayContent |
delta の代わりに表示するテキスト。省くと元のテキストが表示される |
決定の制御はなく、メッセージをブロックしたり、記録や Claude に送られるものを変えたりはできません。Claude Code は JSON 出力の displayContent に従い、systemMessage と continue は捨てます。
次の例は、プレーンテキストの表示のために、Claude の応答から Markdown の書式を取り除きます。スクリプトは各まとまりを stdin から読み、delta から太字の記号とインラインコードのバッククォートを除いて、結果を displayContent として返します。設定ファイルにコマンドフックを登録します(macOS・Linux)。
{
"hooks": {
"MessageDisplay": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/plain-display.sh",
"args": []
}
]
}
]
}
}
スクリプトをプロジェクトの .claude/hooks/plain-display.sh に保存し、chmod +x で実行権限を付けます。
#!/bin/bash
jq '{hookSpecificOutput: {hookEventName: "MessageDisplay", displayContent: (.delta | gsub("\\*\\*"; "") | gsub("`"; ""))}}'
Windows では、command を powershell.exe、args を -NoProfile・-ExecutionPolicy・Bypass・-File・${CLAUDE_PROJECT_DIR}/.claude/hooks/plain-display.ps1 にして、次のスクリプトを動かします。
$batch = [Console]::In.ReadToEnd() | ConvertFrom-Json
$text = $batch.delta -replace '\*\*', '' -replace '`', ''
@{
hookSpecificOutput = @{
hookEventName = "MessageDisplay"
displayContent = $text
}
} | ConvertTo-Json
Markdown の無いまとまりは、そのまま通ります。スクリプトが失敗した場合(jq が無いなど)、Claude Code は元のテキストを表示し、失敗はデバッグ出力にだけ記録され、セッションには出ません。
PreToolUse#
Claude がツールのパラメーターを作った後、ツール呼び出しを処理する前に動きます。EndConversation を除くあらゆるツール名に一致します:Bash・PowerShell・Edit・Write・Read・Glob・Grep・Agent・Workflow・WebFetch・WebSearch・AskUserQuestion・ExitPlanMode などの組み込みツールと、すべての MCP ツール名です。
- 特定のファイルがディスク上で変わったとき、誰が書いたかにかかわらずフックを動かすには、ファイルを編集するツールを名前で一致させるのではなく
FileChangedを使います。FileChangedのフックは変更の後に動き、決定の制御が無いので、書き込みをブロックできません - 決定の制御(次の節)で、ツール呼び出しを許可・拒否・確認・保留できます
PreToolUseの Agent SDK のコールバックフックがタイムアウトを超えると、ツール呼び出しをブロックし、Claude はタイムアウトを名指しするエラーの結果を受け取ります。別のフックが返した明示的な拒否は、それでも優先されます
注意
PreToolUse は、Claude がツールを呼んだときだけ動きます。プロンプトで @ を使って参照したファイルは、ツール呼び出しなしで追加されます(Claude Code がプロンプトを組み立てるときに内容を挿入するため)。そのため、Read に一致するフックを含め、PreToolUse のフックは発火しません。@ 参照から特定のパスをブロックするには、代わりに Read の拒否ルールを使います。EndConversation でも PreToolUse は発火しません。
PreToolUse の入力#
共通の入力フィールドに加えて、tool_name・tool_input・tool_use_id を受け取ります。
- MCP ツールでは、サーバーの
nameと、サーバー定義の出どころを示すsourceを持つオブジェクトmcp_serverも入力に入ります。sourceの値にはplugin・sdk・userやprojectなどの設定の範囲があります。信頼の判断は、nameやmcp__<server>__のツール名の接頭辞でなくsourceに基づいて行います。mcp_serverフィールドは v2.1.274 以降が必要です - ファイルツール
Write・Edit・Readでは、tool_input.file_pathは常に絶対パスです。- Claude Code は、フックが動く前に
~と相対パスを展開するので、パスに一致させるフックを、~や同じパスの相対の書き方で回避できない - Windows では、フックが
$PWDが/c/projectのように見える Git Bash で動いていても、パスはバックスラッシュ区切りで届く /src/の確認のようにスラッシュで書いた比較は、バックスラッシュのパスには決して一致せず、ツール呼び出しは、フックにブロックするものが無かったかのように進む- 比較の前に区切りを正規化する(Bash なら
FILE_PATH="${FILE_PATH//\\//}"、Python ならfile_path.replace("\\", "/"))。そのうえで、パスが絶対パスなので^で固定せず、/src/のようなパスの一部に一致させる
- Claude Code は、フックが動く前に
Windows の Write 呼び出しは、次の形で届きます。
{
"hook_event_name": "PreToolUse",
"tool_name": "Write",
"tool_input": {
"file_path": "C:\\project\\src\\index.ts",
"content": "..."
},
...
}
tool_input のフィールドはツールで決まります。
Bash
シェルコマンドを実行します。
| フィールド | 型 | 例 | 内容 |
|---|---|---|---|
command |
string | "npm test" |
実行するシェルコマンド |
description |
string | "Run test suite" |
コマンドの内容の任意の説明 |
timeout |
number | 120000 |
任意のタイムアウト(ミリ秒)。最大値を超える値は、拒否されず最大値に下げられる |
run_in_background |
boolean | false |
コマンドをバックグラウンドで動かすか |
Bash コマンドが Git リポジトリのファイルを変更したとき、Claude Code は変更内容を記録できます。
bashEditDiffEnabled設定が記録をオンにしているときは、すべての権限モードで記録します(どのファイルで設定できるかは、その設定の項目にあります)。そうでなければ、auto モードとbypassPermissionsモードで、かつ Claude Code が Claude に Bash でのファイル編集を指示するときだけ記録します。記録をオフにするにはbashEditDiffEnabledをfalseにします。バックグラウンドのコマンドと読み取り専用のコマンドには差分がありません- その後
PostToolUseのフックは、変更されたファイルをtool_response.bashEditDiffで受け取ります。一覧は、コマンドの実行中にリポジトリの下で変わったものです。Git が無視するファイルとサブモジュールのファイルは載りません。v2.1.269 以降が必要です
補足
この一覧は最善努力で、パブリックベータです。Claude Code は変更を見逃したり、同時に別のプロセスが変えたファイルを含めたり、サイズの上限で止まったりすることがあります。フィールドの形は変わりうるので、一覧は、レビューすべきものを見つけるために使い、方針の強制には使いません。
changedFiles と files はコマンドが変えたものを挙げ、残りのフィールドはその一覧の完全さと信頼性を示します。
| フィールド | 型 | 例 | 内容 |
|---|---|---|---|
changedFiles |
array | ["/path/to/src/app.ts"] |
コマンドが変更したファイルの絶対パス(最大 200)。files に差分があるか、moreFiles が 0 より大きいときに存在する |
files |
array | [{"filePath": "/path/to/src/app.ts", "hunks": [...]}] |
表示用の、変更されたファイル最大 5 つの差分。コマンドが追加・削除したファイルでは、created か deleted が true |
moreFiles |
number | 2 |
files に差分が無い、変更されたファイルの数 |
unavailable |
boolean | true |
差分が不完全か、取得できなかったときに設定される |
skipped |
boolean | true |
git checkout や git stash のように作業ツリーを動かす Git コマンドで設定され、Claude Code は差分を取らない |
shared |
boolean | true |
サブエージェントなど別の Bash ツール呼び出しが同じリポジトリで同時に動いたときに設定され、挙げた変更の一部がそのコマンドのものかもしれない |
PowerShell
PowerShell コマンドを実行します(プラットフォームごとの利用可否はツール一覧を参照)。フィールドは Bash ツールと同じで、コマンドの文字列は command に入ります。
| フィールド | 型 | 例 | 内容 |
|---|---|---|---|
command |
string | "Get-ChildItem -Recurse" |
実行する PowerShell コマンド |
description |
string | "List files recursively" |
コマンドの内容の任意の説明 |
timeout |
number | 120000 |
任意のタイムアウト(ミリ秒) |
run_in_background |
boolean | false |
コマンドをバックグラウンドで動かすか |
シェルコマンドを調べるフックでは、両方のツールを対象にするため Bash|PowerShell に一致させます。
- PowerShell ツールが有効な Windows では、Claude は PowerShell を主なシェルとして扱い、シェルコマンドをそれ経由で動かす
- Git Bash の無い Windows では、ツールは自動で有効になり、Claude Code は Bash ツールをまったく登録しない
Bashだけに一致するフックは、そこでは発火しない
Write
ファイルを作成または上書きします。
| フィールド | 型 | 例 | 内容 |
|---|---|---|---|
file_path |
string | "/path/to/file.txt" |
書き込むファイルの絶対パス |
content |
string | "file content" |
ファイルに書く内容 |
Edit
既存のファイルの文字列を置換します。
| フィールド | 型 | 例 | 内容 |
|---|---|---|---|
file_path |
string | "/path/to/file.txt" |
編集するファイルの絶対パス |
old_string |
string | "original text" |
探して置換するテキスト |
new_string |
string | "replacement text" |
置換後のテキスト |
replace_all |
boolean | false |
すべての出現箇所を置換するか |
Read
ファイルの内容を読みます。
| フィールド | 型 | 例 | 内容 |
|---|---|---|---|
file_path |
string | "/path/to/file.txt" |
読むファイルの絶対パス |
offset |
number | 10 |
読み始める行番号(任意) |
limit |
number | 50 |
読む行数(任意) |
Glob
glob パターンに一致するファイルを探します。
| フィールド | 型 | 例 | 内容 |
|---|---|---|---|
pattern |
string | "**/*.ts" |
ファイルを照合する glob パターン |
path |
string | "/path/to/dir" |
検索するディレクトリ(任意)。既定は現在の作業ディレクトリ |
Grep
正規表現でファイルの内容を検索します。
| フィールド | 型 | 例 | 内容 |
|---|---|---|---|
pattern |
string | "TODO.*fix" |
検索する正規表現 |
path |
string | "/path/to/dir" |
検索するファイルかディレクトリ(任意) |
glob |
string | "*.ts" |
ファイルを絞る glob パターン(任意) |
output_mode |
string | "content" |
"content"・"files_with_matches"・"count"。既定は "files_with_matches" |
-i |
boolean | true |
大文字小文字を区別しない検索 |
multiline |
boolean | false |
複数行の一致を有効にする |
WebFetch
Web のコンテンツを取得して処理します。
| フィールド | 型 | 例 | 内容 |
|---|---|---|---|
url |
string | "https://example.com/api" |
コンテンツを取得する URL |
prompt |
string | "Extract the API endpoints" |
取得したコンテンツに対して実行するプロンプト |
WebSearch
Web を検索します。
| フィールド | 型 | 例 | 内容 |
|---|---|---|---|
query |
string | "react hooks best practices" |
検索クエリ |
allowed_domains |
array | ["docs.example.com"] |
任意:これらのドメインの結果だけを含める |
blocked_domains |
array | ["spam.example.com"] |
任意:これらのドメインの結果を除く |
Agent
サブエージェントを起動します。
| フィールド | 型 | 例 | 内容 |
|---|---|---|---|
prompt |
string | "Find all API endpoints" |
エージェントが行うタスク |
description |
string | "Find API endpoints" |
タスクの短い説明 |
subagent_type |
string | "Explore" |
使う専門のエージェントの種類 |
model |
string | "sonnet" |
既定を上書きする任意のモデルのエイリアス |
フォアグラウンドの Agent 呼び出しが完了すると、PostToolUse のフックは、サブエージェントの結果と実行のテレメトリを tool_response で受け取ります。サブエージェント全体のトークンとコストの集計には、query_source が "subagent" のトークンとコストのカウンターを使います(totalTokens と usage は最後のリクエストだけが対象のため)。
| フィールド | 型 | 例 | 内容 |
|---|---|---|---|
status |
string | "completed" |
フォアグラウンドのサブエージェントは "completed"、バックグラウンドのサブエージェントは "async_launched"。サブエージェントは既定でバックグラウンドで動くので、run_in_background を省いた Agent の呼び出しも "async_launched" になる |
agentId |
string | "a4d2c8f1e0b3a297" |
サブエージェントの実行の識別子 |
content |
array | [{"type": "text", "text": "Found 12 endpoints..."}] |
サブエージェントの最後のテキストブロック。報告が SubagentHandback 経由のサブエージェントでは、その代わりに、その受け渡しについての短い注記 |
resolvedModel |
string | "claude-sonnet-4-5" |
サブエージェントが始まったモデル。要求したモデルと違うことがある |
modelsUsed |
array | ["claude-sonnet-4-5", "claude-haiku-4-5"] |
使ったモデルを順に並べたもの(連続する重複は1つにまとめる)。実行の途中でモデルが入れ替わったときだけ設定される。v2.1.212 以降 |
totalTokens |
number | 12450 |
サブエージェントの最後の API リクエストのトークン数(入力・出力・キャッシュの合計)。実行全体の合計ではない |
totalDurationMs |
number | 48211 |
サブエージェントの実行の実時間 |
totalToolUseCount |
number | 7 |
サブエージェントが行ったツール呼び出しの数 |
usage |
object | {"input_tokens": 8320, ...} |
最後の API リクエストの種類別のトークンの内訳:input_tokens・output_tokens・cache_creation_input_tokens・cache_read_input_tokens |
- v2.1.271 以降では、Claude Code が auto モードで提供する
SubagentHandbackツールで動くサブエージェントは、報告をテキストで返さず、そのツール経由で届けます。そのcompletedの結果のcontentフィールドには、報告そのものでなく受け渡しの短い注記が入ります。報告を読むには、SubagentHandbackに一致するPreToolUseかPostToolUseのフックを使い、tool_input.messageを読みます - バックグラウンドのサブエージェントでは、タスクがバックグラウンドに移った時点でツールが返るので、
tool_responseに使用量のフィールドはありません(バックグラウンドの起動は即座に返り、実行の途中で Claude Code がバックグラウンドに移したフォアグラウンドのタスクは、その移行時に返ります)。status: "async_launched"・agentId・description・prompt・outputFile・resolvedModelを持ちます completedのレスポンスでは、resolvedModelはサブエージェントが始まったモデルで、availableModelsなどの上書きが働くと、tool_inputのmodelの値と違うことがあります。async_launchedのレスポンスでは、エージェントがバックグラウンドに移った時点で使っていたモデルなので、バックグラウンド化の前に起きた入れ替わりが反映されます。modelsUsedとバックグラウンド化時点のresolvedModelの挙動は v2.1.212 以降が必要です
AskUserQuestion
ユーザーに、1〜4 個の選択式の質問をします。
| フィールド | 型 | 例 | 内容 |
|---|---|---|---|
questions |
array | [{"question": "Which framework?", "header": "Framework", "options": [{"label": "React", "description": "Component library"}, {"label": "Vue", "description": "Progressive framework"}], "multiSelect": false}] |
提示する質問。それぞれ question 文字列・短い header・options 配列・任意の multiSelect フラグを持つ |
answers |
object | {"Which framework?": "React"} |
任意。質問のテキストから選ばれた選択肢のラベルへの対応。複数選択の答えは、ラベルをカンマでつなぐ。Claude はこのフィールドを設定せず、プログラムで答えるには updatedInput で渡す |
ExitPlanMode
Claude がプランモードを抜ける前に、計画を示して、ユーザーに承認を求めます。Claude はツールを呼ぶ前に計画をディスク上のファイルに書くので、モデルからの tool_input そのものは通常空です。Claude Code が、フックに入力を渡す前に、計画の内容とファイルパスを注入します。
| フィールド | 型 | 例 | 内容 |
|---|---|---|---|
plan |
string | "## Refactor auth\n1. Extract..." |
Markdown の計画の内容。ディスク上の計画ファイルから注入される |
planFilePath |
string | "/Users/.../plans/refactor-auth.md" |
計画ファイルのパス。注入される |
allowedPrompts |
array | [{"tool": "Bash", "prompt": "run tests"}] |
非推奨。Claude Code は受け付けるが無視する。v2.1.205 より前は、計画を実装するために Claude が要求した、プロンプトに基づく権限を持っていた |
PostToolUse では、tool_response は、承認された計画を持つ plan と filePath のフィールドと、内部の状態フラグを持つオブジェクトです。計画の内容は、ディスクからファイルを読み直さず tool_response.plan を読みます。
PreToolUse の決定の制御#
ツール呼び出しを進めるかを制御できます。最上位の decision フィールドを使うほかのフックと違い、PreToolUse は決定を hookSpecificOutput オブジェクトの中に返します。これにより、4つの結果(allow・deny・ask・defer)と、実行前にツールの入力を変更する機能で、より豊かな制御ができます。
| フィールド | 内容 |
|---|---|
permissionDecision |
"allow" は権限確認を省く。ただし、どのモードも自動承認しない操作と、updatedInput を併せる必要がある AskUserQuestion と ExitPlanMode は除く。"deny" はツール呼び出しを防ぐ。"ask" はユーザーに確認を求める。"defer" は穏やかに終了し、あとでツールを再開できるようにする。拒否・確認のルールは、フックが何を返しても評価される |
permissionDecisionReason |
"ask" では、権限の確認でユーザーに見える。-p の実行で確認に答える人がおらず、Claude Code が呼び出しを拒否したときは、代わりに Claude がツールの結果で理由を読む。"deny" では Claude に見える。"allow" と "defer" では、デバッグログにだけ書かれる |
updatedInput |
実行前にツールの入力パラメーターを変更する。入力オブジェクト全体を置き換えるので、変更しないフィールドも一緒に入れる。Claude Code は、権限ルールと Bash コマンドのバックグラウンド化の適格性を、Claude が送った入力でなく、フックが返した入力に対して評価する。自動承認なら "allow"、変更後の入力をユーザーに見せるなら "ask" と組み合わせる。"defer" では無視される |
additionalContext |
ツールの結果と並んで Claude の文脈に加わる文字列。permissionDecision が "defer" のときは無視される |
- 複数の PreToolUse のフックが異なる判定を返すときの優先順位は
deny>defer>ask>allowです - 終了コード 2 でブロックするフックも
"deny"と同じ経路をたどり、Claude は stderr のメッセージを拒否の理由として見ます - フックが
"ask"を返すと、ユーザーに出る権限確認に、フックの出どころを示すラベルが付きます:設定ファイルかエージェントの frontmatter のフックは[settings]、プラグインのフックは[plugin:<name>]、スキルの frontmatter のフックは[skill] - フックの
"ask"は auto モードでも権限確認を強制します。分類器はツール呼び出しを拒否できますが、黙って承認はできません。v2.1.211 より前は、分類器が、サンドボックスの外で動く Bash コマンドを、フックが要求した確認を出さずに承認できました(分類器は、そのコマンドに自分の安全規則を適用し、フックの"deny"は常に守られました)
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": "My reason here",
"updatedInput": {
"field_to_modify": "new value"
},
"additionalContext": "Current environment: production. Proceed with caution."
}
}
ユーザーの操作が要るツール#
AskUserQuestion と ExitPlanMode は、ユーザーの操作が要ります。-p フラグの非対話モードで、Claude Code がこれらを出すのは、実行に、確認を受ける権限ホスト(Agent SDK の canUseTool コールバックなど)があるときだけです。
PreToolUse のフックは、次のことをすると、その要件を満たします。
- ツールの入力を stdin から読む
- 自前の UI で答えを集める
- 答えを入れた
updatedInputと一緒にpermissionDecision: "allow"を返す。ツールは確認なしで動く
これらのツールでは、"allow" だけでは足りません。
AskUserQuestion では、元の questions 配列を返し、各質問のテキストから選ばれた答えへの対応を持つ answers オブジェクトを足します。次の出力は、1つの質問に React と答えます。
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"updatedInput": {
"questions": [
{
"question": "Which framework?",
"header": "Framework",
"options": [{"label": "React", "description": "Component library"}, {"label": "Vue", "description": "Progressive framework"}],
"multiSelect": false
}
],
"answers": {"Which framework?": "React"}
}
}
}
サーバーが _meta["anthropic/requiresUserInteraction"] で印を付けた MCP ツールは、より厳しい扱いです。Claude Code は、フックがツールの必要とする操作を集めたことを確認できないので、フックは、updatedInput の有無にかかわらず、"allow" でその承認確認を飛ばせません。
補足
PreToolUse は以前、最上位の decision と reason を使っていましたが、このイベントでは非推奨です。代わりに hookSpecificOutput.permissionDecision と hookSpecificOutput.permissionDecisionReason を使います。非推奨の値 "approve" と "block" は、それぞれ "allow" と "deny" に対応します。PostToolUse や Stop などのほかのイベントは、最上位の decision と reason が現在の形式です。
ツール呼び出しを後に保留する(defer)#
"defer" は、claude -p をサブプロセスとして動かして JSON 出力を読む統合(Agent SDK のアプリや、Claude Code の上に作ったカスタム UI など)向けです。呼び出し側のプロセスが、ツール呼び出しで Claude を一時停止し、自前のインターフェースで入力を集め、止まったところから再開できます。Claude Code がこの値を守るのは、-p フラグの非対話モードだけです。対話セッションでは、警告をログに出し、フックの結果を無視します。
典型は AskUserQuestion ツールです。Claude がユーザーに何か尋ねたいが、答えるターミナルが無い場合です。-p の実行が AskUserQuestion を出すのは、--permission-prompt-tool で渡す MCP ツールのような権限ホストがあるときだけなので、実行を、それを付けて始めます。往復の流れは次のとおりです。
- Claude が
AskUserQuestionを呼ぶ。PreToolUseのフックが発火する - フックが
permissionDecision: "defer"を返す。ツールは実行されない。プロセスは、保留中のツール呼び出しを記録に残したままstop_reason: "tool_deferred"で終了する - 呼び出し側のプロセスが SDK の結果から
deferred_tool_useを読み、質問を自前の UI に出して、答えを待つ - 呼び出し側のプロセスが、同じ権限ホストで
claude -p --resume <session-id>を実行する。同じツール呼び出しでPreToolUseが再び発火する - フックが、答えを
updatedInputに入れたpermissionDecision: "allow"を返す。ツールが実行され、Claude が続ける
deferred_tool_use フィールドは、ツールの id・name・input を持ちます。input は、実行前に捕まえた、Claude がそのツール呼び出しのために生成したパラメーターです。
{
"type": "result",
"subtype": "success",
"stop_reason": "tool_deferred",
"session_id": "abc123",
"deferred_tool_use": {
"id": "toolu_01abc",
"name": "AskUserQuestion",
"input": { "questions": [{ "question": "Which framework?", "header": "Framework", "options": [{"label": "React", "description": "Component library"}, {"label": "Vue", "description": "Progressive framework"}], "multiSelect": false }] }
}
}
- タイムアウトも再試行の上限もありません。セッションは、再開するまでディスクに残ります(既定でセッションファイルを 30 日後に削除する
cleanupPeriodDaysの保持の掃除の対象)。再開したとき答えがまだ用意できていなければ、フックは再び"defer"を返せて、プロセスは同じように終了します。ループをいつ抜けるかは、呼び出し側が、最終的にフックから"allow"か"deny"を返して決めます "defer"が働くのは、そのターンに Claude がツール呼び出しを1つだけ行うときです。Claude が複数のツール呼び出しを同時に行うと、"defer"は警告つきで無視され、ツールは通常の権限の流れで進みます。再開では1つのツールしか再実行できず、バッチの1つだけを保留して残りを未解決のままにする方法が無いためです- 再開したときに保留したツールが使えなくなっていると、プロセスは、フックが発火する前に
stop_reason: "tool_deferred_unavailable"とis_error: trueで終了します。そのツールを提供していた MCP サーバーが、再開したセッションで接続されていないときに起きます。どのツールが無くなったか分かるよう、deferred_tool_useのペイロードは含まれます
補足
保留したセッションをプランモードで再開するには、Claude Code が承認のための計画を提示できるよう、--resume と一緒に --permission-prompt-tool を渡します。ほかの特定の起動フラグを渡すと、再開した実行はプランモードに戻りません。v2.1.246 以降が必要です。-p で再開するとき、Claude Code は、保存されているほかの権限モードは復元しません。新しい claude -p の実行が始まる権限モードで始めるので、保留したセッションがモードを使っていたなら、--permission-mode か --dangerously-skip-permissions をもう一度渡します。-p なしで claude --resume <session-id> で再開すると、Claude Code は保存された権限モードを復元します(セッションに挙げた例外を除く)。
PermissionRequest#
Claude Code がツールを使う許可をあなたに尋ねようとするときに動きます。バックグラウンドのサブエージェントの非対話モードなど、確認を出せないセッションでも、Claude Code はこれらのフックを動かし、どのフックも判定を返さなければ、ツール呼び出しを拒否します。決定の制御で、ユーザーに代わって許可や拒否ができます。--permission-prompt-tool や Agent SDK の canUseTool コールバックに届く呼び出しでは、フックはホストと並んで動き、先に判定したほうが適用されます。
- このイベントは、Claude がツールを使う許可を求めた瞬間の合図が要るときに使います。
permission_promptのNotificationフックは、確認が約6秒待たれた後にだけ動きます - Claude Code は、サンドボックス内のコマンドのネットワークリクエストでは PermissionRequest のフックを動かしません。その確認の合図には、
permission_promptの通知の種類を使います - ツール名に一致します(値は PreToolUse と同じ)
PermissionRequest の入力#
PreToolUse のフックと同じ tool_name と tool_input を受け取りますが、tool_use_id はありません。MCP ツールでは、mcp_server オブジェクトも受け取ります。任意の permission_suggestions 配列は、このリクエストに Claude Code が提案する権限の更新(許可ルールの追加や権限モードの変更など)を持ちます。
permission_suggestions配列は、見える選択肢の正確な一覧ではありません。各権限ダイアログが自分の選択肢を作るためです。ファイル編集のダイアログのように、配列をまったく読まず、リクエストそのものから選択肢を導くものもあります。配列を読むダイアログでも、提案が配列に残っている選択肢を出さないことがあります(allowManagedPermissionRulesOnlyがルールを保存する選択肢を隠す場合など)。また、Yes, and switch to auto mode のように、権限の更新を通さず権限モードを直接変える、提案のエントリが無い選択肢を出すこともあります- PreToolUse のフックは、権限が要るかにかかわらず、すべてのツール呼び出しの前に動きます。PermissionRequest のフックは、Claude Code があなたに権限を尋ねようとするとき、または確認できない呼び出しを自動拒否しようとするときだけ動きます。どちらのイベントも
EndConversationでは発火しません
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "PermissionRequest",
"tool_name": "Bash",
"tool_input": {
"command": "rm -rf node_modules",
"description": "Remove node_modules directory"
},
"permission_suggestions": [
{
"type": "addRules",
"rules": [{ "toolName": "Bash", "ruleContent": "rm -rf node_modules" }],
"behavior": "allow",
"destination": "localSettings"
}
]
}
PermissionRequest の決定の制御#
権限リクエストを許可・拒否できます。全フックが使える JSON 出力のフィールドに加えて、次のイベント固有のフィールドを持つ decision オブジェクトを返せます。
| フィールド | 内容 |
|---|---|
behavior |
"allow" で権限を与え、"deny" で拒否する。拒否・確認のルールは引き続き評価されるので、"allow" を返すフックは、一致する拒否ルールを上書きしない |
updatedInput |
"allow" のときだけ:実行前にツールの入力パラメーターを変更する。入力オブジェクト全体を置き換えるので、変更しないフィールドも入れる。変更後の入力は、拒否・確認のルールに対して再評価される |
updatedPermissions |
"allow" のときだけ:適用する権限の更新のエントリの配列(許可ルールの追加やセッションの権限モードの変更など) |
message |
"deny" のときだけ:権限が拒否された理由を Claude に伝える |
interrupt |
"deny" のときだけ:true なら Claude を止める |
decision オブジェクトなしで終了コード 2 で終わるフックは、権限の流れを変えず、その stderr は捨てられます。リクエストを許可・拒否できるのは decision オブジェクトだけです。
{
"hookSpecificOutput": {
"hookEventName": "PermissionRequest",
"decision": {
"behavior": "allow",
"updatedInput": {
"command": "npm run lint"
}
}
}
}
権限の更新のエントリ#
出力の updatedPermissions と入力の permission_suggestions は、同じエントリの配列を使います。各エントリは、ほかのフィールドを決める type と、変更の書き込み先を制御する destination を持ちます。
type |
フィールド | 効果 |
|---|---|---|
addRules |
rules・behavior・destination |
権限ルールを追加する。rules は {toolName, ruleContent?} のオブジェクトの配列。ruleContent を省くとツール全体に一致する。behavior は "allow"・"deny"・"ask" |
replaceRules |
rules・behavior・destination |
destination の、指定した behavior のルールをすべて、与えた rules に置き換える |
removeRules |
rules・behavior・destination |
指定した behavior の、一致するルールを取り除く |
setMode |
mode・destination |
権限モードを変える。有効なモードは default・auto・acceptEdits・dontAsk・bypassPermissions・plan、および default の別名 manual(v2.1.200 以降) |
addDirectories |
directories・destination |
作業ディレクトリを追加する。directories はパス文字列の配列 |
removeDirectories |
directories・destination |
作業ディレクトリを取り除く |
補足
bypassPermissions の setMode が有効になるのは、バイパスモードがすでに使える状態でセッションを起動した場合だけです(--dangerously-skip-permissions・--permission-mode bypassPermissions・--allow-dangerously-skip-permissions、またはユーザー設定・--settings・管理設定の permissions.defaultMode: "bypassPermissions")。そうでなければ、更新は何もしません。permissions.disableBypassPermissionsMode がモードを無効にしているときと、セッションが制限モードで始まったときも何もしません。bypassPermissions は、destination にかかわらず defaultMode として保存されません。
destination は、変更がメモリにとどまるか、設定ファイルに保存されるかを決めます。
destination |
書き込み先 |
|---|---|
session |
メモリ内のみ。セッションが終わると捨てられる |
localSettings |
.claude/settings.local.json |
projectSettings |
.claude/settings.json |
userSettings |
~/.claude/settings.json |
フックは、受け取った permission_suggestions の1つを、自分の updatedPermissions の出力としてそのまま返せます。
PostToolUse#
ツールが成功して完了した直後に動きます。ツール名に一致します(値は PreToolUse と同じ)。ツール名が適切な絞り込みでないときは、より広く一致させます。
- 成功したあらゆるツールの後にフックを動かすには、
matcherを省くか"*"にします。フックが何が変わったかを自分で見つけられます(git status --porcelainを動かすなど。git diffが見逃す未追跡ファイルも出ます)。失敗したツール呼び出しには、同じフックをPostToolUseFailureの下にも足します - 特定のファイルがディスク上で変わったとき、誰が書いたかにかかわらずフックを動かすには、
FileChangedを使います。Bashコマンドや Claude Code の外のプロセスが同じファイルを書き換えたとき、Claude Code はEdit|Writeに一致するPostToolUseのフックを動かしません
PostToolUse の入力#
ツールがすでに成功して実行された後に発火します。入力には、ツールに送った引数の tool_input と、返された結果の tool_response の両方が入ります。両方の厳密なスキーマはツールで決まります。ファイルツールの tool_input のパスは、PreToolUse と同じ形式(常に絶対パスで、プラットフォームのネイティブな区切り。Windows ではバックスラッシュ)で届きます。MCP ツールでは、mcp_server オブジェクトも入力に入ります。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "PostToolUse",
"tool_name": "Write",
"tool_input": {
"file_path": "/path/to/file.txt",
"content": "file content"
},
"tool_response": {
"filePath": "/path/to/file.txt",
"type": "create"
},
"tool_use_id": "toolu_01ABC123...",
"duration_ms": 12
}
| フィールド | 内容 |
|---|---|
duration_ms |
任意。ツールの実行時間(ミリ秒)。権限確認と PreToolUse のフックにかかった時間は含まない |
PostToolUse の決定の制御#
ツールの実行後に Claude へフィードバックを出せます。全フックが使える JSON 出力のフィールドに加えて、次のイベント固有のフィールドを返せます。
| フィールド | 内容 |
|---|---|
decision |
"block" で、reason をツールの結果の隣に加える。Claude は元の出力もそのまま見る。置き換えるには updatedToolOutput を使う |
reason |
decision が "block" のとき、Claude に出す説明 |
additionalContext |
ツールの結果と並んで Claude の文脈に加わる文字列 |
classifierContext |
Claude にでなく、auto モードの分類器へ向けた、この呼び出しの結果についての短い注記。v2.1.236 以降 |
updatedToolOutput |
Claude に送る前に、ツールの出力を与えた値に置き換える。値はツールの出力の形に合っていなければならない |
updatedMCPToolOutput |
MCP ツールの出力だけを置き換える。すべてのツールで働く updatedToolOutput を使うことを勧める |
Bash の呼び出しの出力を置き換える例です。置換する値は Bash ツールの出力の形に合っています。
{
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": "Additional information for Claude",
"updatedToolOutput": {
"stdout": "[redacted]",
"stderr": "",
"interrupted": false,
"isImage": false
}
}
}
注意
updatedToolOutput が変えるのは Claude が見るものだけです。フックが発火した時点でツールはすでに動いているので、書かれたファイル・実行されたコマンド・送られたネットワークリクエストは、すでに効いています。OpenTelemetry のツールスパンや分析イベントなどのテレメトリも、フックが動く前の元の出力を記録します。ツール呼び出しを実行前に防いだり変更したりするには、PreToolUse のフックを使います。置換する値は、ツールの出力の形に合っている必要があります。組み込みツールは、プレーンな文字列でなく構造化されたオブジェクトを返します(たとえば Bash は stdout・stderr・interrupted・isImage のフィールドを持つオブジェクト)。組み込みツールでは、ツールの出力スキーマに合わない値は無視され、元の出力が使われます。MCP ツールの出力は、スキーマ検証なしで通ります。Claude が必要とするエラーの詳細を取り除くと、Claude が誤った前提で進むことがあります。
auto モードの分類器へ結果を注釈する#
classifierContext を返すと、ツール呼び出しの結果についての短い注記を、Claude にでなく auto モードの分類器へ送ります。分類器はツールの結果そのものを受け取らないので、このフィールドは、分類器が後の操作を審査する前に、呼び出しが何を返したかを伝える、サポートされた方法です。v2.1.236 以降が必要です。クエリの出力がどこから来たかを分類器に伝える例:
{
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"classifierContext": "This query ran against the staging database, not production."
}
}
分類器がその注記にどれだけ重みを置くかは、フックを設定した場所で決まります。
- Claude Code で設定したフック(設定ファイル・プラグイン・スキル・エージェントの frontmatter):分類器は、注記を、アプリケーションが与えた、未検証の文脈として扱う。注記がユーザーの意図を確立することは無く、あなたが承認した・要求したと主張していれば、分類器はその主張を、会話の中のあなた自身のメッセージと照らして確かめる
- プロセス内の Agent SDK のコールバック:Claude Code を組み込んだアプリケーションが、フックを TypeScript SDK のコールバックとして登録し、実行中のセッションで注記を返すと、分類器は、注記で中継されたユーザーの発言をユーザーの意図として重く見ることがある。そうした発言は、あなたが送るメッセージなら分類器が受け入れる同意の要件を満たせるが、あなた自身のメッセージでも解けないブロックは、解けない。セッションの再開後、Claude Code は復元した注記を未検証の文脈として扱う。両方のグループのフックが同じ呼び出しに注釈を付けると、分類器は、結合した注記を未検証として扱う
注記を渡すとき、Claude Code は次の制限を適用します。
- 長さ:1つのツール呼び出しの注記を 2,000 文字に制限し、残りは切り詰める。この上限は、その呼び出しに応答するすべてのフックで共有される
- 同期のレスポンスだけ:バックグラウンドで動くフックのレスポンスのこのフィールドは無視される(そのレスポンスは、Claude Code がツールの結果を記録した後に届くため)
- 分類器が記録しない呼び出し:分類器の記録は、ファイルの読み取りや検索のような読み取り専用の参照を省く。そうした呼び出しに付けた注記は捨てられる
- 書き換えとの関係:
updatedToolOutputで置き換える出力についての注記は、同じフックのレスポンスで両方のフィールドを返す。その書き換えが拒否されたり、別のフックの書き換えが置き換えたりすると、注記は捨てられる。書き換えなしで返した注記は、別のフックが出力を書き換えても渡される
注意
分類器は、classifierContext に入れた内容を、セッションをホストするアプリケーションからの情報として読みます。信頼できないツールの出力やサードパーティのテキストをそこへコピーしないでください。注記は、この1つの呼び出しについての短い主張(出どころの事実や、それについてのユーザーの発言)にとどめ、無関係なメッセージやイベントの流れを届けるのに使わないでください。
PostToolUseFailure#
実行を始めたツールが失敗したとき(ツールがエラーを投げたとき、または MCP ツールがエラーの結果を返したとき)に動きます。失敗の記録・警告の送信・Claude への是正のフィードバックに使います。ツール名に一致します(値は PreToolUse と同じ)。
補足
このイベントは、実行前に拒否されたツール呼び出しでは発火しません:未知のツール名・スキーマやツール固有の検証に失敗する入力・権限の拒否です。検証による拒否は tool_use_error の結果として返り、フックが動く前に起きるので、PreToolUse も PostToolUseFailure も発火しません。権限の拒否は PreToolUse を発火しますが、このイベントは発火しません(PermissionDenied を参照)。
PostToolUseFailure の入力#
PostToolUse と同じ tool_name と tool_input に加えて、エラーの情報を最上位のフィールドで受け取ります。MCP ツールでは mcp_server オブジェクトも受け取ります。失敗した npm test コマンドの例:
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "PostToolUseFailure",
"tool_name": "Bash",
"tool_input": {
"command": "npm test",
"description": "Run test suite"
},
"tool_use_id": "toolu_01ABC123...",
"error": "Exit code 1\nError: Cannot find module 'express'",
"is_interrupt": false,
"duration_ms": 4187
}
| フィールド | 内容 |
|---|---|
error |
何が失敗したかを説明する文字列。形式は失敗したツールで決まる |
is_interrupt |
任意の真偽値。失敗が、ツールが報告したエラーとしてでなく、中断として Claude Code に届いたとき true。実行中のツールのキャンセルではこのフックは発火せず、代わりにツールの結果が中断のメッセージを持つ |
duration_ms |
任意。ツールの実行時間(ミリ秒)。権限確認と PreToolUse のフックにかかった時間は含まない |
errorの文字列は、一般に、失敗したツールの結果として Claude が受け取るのと同じテキストです。形式はツールと失敗で変わります。フックは、まずtool_name・is_interrupt・Exit code Nの最初の行を手がかりにし、文字列の残りは、安定した形式でなく表示用のテキストとして扱います- Bash と PowerShell では、実行されて終了したコマンドは、最初の行が
Exit code Nで、続いてコマンドが出した出力が、stdout と stderr を交互に混ぜた1つのブロックになります - Claude Code がシェルのプロセス自体を起動できなかったときは、終了コードの行の無い、単なる失敗メッセージが入ることもあります
- Claude Code は、長い文字列を
... [N characters truncated] ...の印の前後で中央を切り詰め、Command timed out after 2m 0sのような自分の行を挿入することもあります
PostToolUseFailure の決定の制御#
ツールの失敗の後に Claude へ文脈を渡せます。全フックが使える JSON 出力のフィールドに加えて、次のフィールドを返せます。
| フィールド | 内容 |
|---|---|
additionalContext |
エラーと並んで Claude の文脈に加わる文字列 |
{
"hookSpecificOutput": {
"hookEventName": "PostToolUseFailure",
"additionalContext": "Additional information about the failure for Claude"
}
}
PostToolBatch#
バッチ内のすべてのツール呼び出しが解決した後、Claude Code が次のリクエストをモデルへ送る前に、1回だけ動きます。PostToolUse はツールごとに1回発火するので、Claude が並行してツール呼び出しをすると同時に発火します。PostToolBatch はバッチ全体を持って1回だけ発火するので、個々のツールでなく、動いたツールの組に依存する文脈を注入するのに向きます。このイベントに matcher はありません。
PostToolBatch の入力#
共通の入力フィールドに加えて、バッチ内のすべてのツール呼び出しを説明する配列 tool_calls を受け取ります。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "PostToolBatch",
"tool_calls": [
{
"tool_name": "Read",
"tool_input": {"file_path": "/.../ledger/accounts.py"},
"tool_use_id": "toolu_01...",
"tool_response": "1\tfrom __future__ import annotations\n2\t..."
},
{
"tool_name": "Read",
"tool_input": {"file_path": "/.../ledger/transactions.py"},
"tool_use_id": "toolu_02...",
"tool_response": "1\tfrom __future__ import annotations\n2\t..."
}
]
}
tool_response は、対応する tool_result ブロックでモデルが受け取るのと同じ内容です。値は、ツールが出したとおりの、直列化された文字列かコンテンツブロックの配列です。Read なら、生のファイルの内容でなく、行番号つきのテキストです。レスポンスは大きいことがあるので、必要なフィールドだけを解析します。
補足
tool_response の形は PostToolUse と違います。PostToolUse は、Write の {filePath: "...", type: "create"} のように、ツールの構造化された Output オブジェクトを渡します。PostToolBatch は、モデルが見る、直列化された tool_result の内容を渡します。
PostToolBatch の決定の制御#
Claude に文脈を注入できます。全フックが使える JSON 出力のフィールドに加えて、次のフィールドを返せます。
| フィールド | 内容 |
|---|---|
additionalContext |
次のモデル呼び出しの前に1回注入される文脈の文字列 |
{
"hookSpecificOutput": {
"hookEventName": "PostToolBatch",
"additionalContext": "These files are part of the ledger module. Run pytest before marking the task complete."
}
}
decision: "block" か continue: false を返すと、次のモデル呼び出しの前にエージェントのループを止めます。ブロックのメッセージは、JSON の reason か stopReason、または終了コード 2 のときの stderr から来ます。記録に警告として見え、会話に残るので、会話が続けば Claude も見ます。
PermissionDenied#
auto モードがツール呼び出しを拒否したときに動きます。auto モードと別の安全チェックが分類器自身のリクエストを拒否したときや、その応答が解析できなかったときの、分類器の判定が無い拒否も含みます。このフックが発火するのは auto モードのときだけです。あなたが権限ダイアログを手動で拒否したとき・PreToolUse のフックが呼び出しをブロックしたとき・deny ルールが一致したときは動きません。拒否の記録・設定の調整・ツール呼び出しを再試行してよいとモデルに伝えるのに使います。ツール名に一致します(値は PreToolUse と同じ)。
PermissionDenied の入力#
共通の入力フィールドに加えて、tool_name・tool_input・tool_use_id・reason を受け取ります。MCP ツールでは mcp_server オブジェクトも受け取ります。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "auto",
"hook_event_name": "PermissionDenied",
"tool_name": "Bash",
"tool_input": {
"command": "rm -rf /tmp/build",
"description": "Clean build directory"
},
"tool_use_id": "toolu_01ABC123...",
"reason": "[Irreversible Local Destruction]"
}
| フィールド | 内容 |
|---|---|
reason |
拒否の理由。分類器の判定では、ほとんどのセッションで、一致したルールを [Data Exfiltration] のように角括弧で示す。判定が無い拒否では Auto mode could not evaluate this action and is blocking it for safety で始まる。分類器のモデルが使えなかったための拒否では、固定のテキスト Classifier unavailable |
PermissionDenied の決定の制御#
拒否されたツール呼び出しをモデルが再試行してよいと伝えられます。hookSpecificOutput.retry を true にした JSON オブジェクトを返します。
{
"hookSpecificOutput": {
"hookEventName": "PermissionDenied",
"retry": true
}
}
retryがtrueのとき、Claude Code は、ツール呼び出しを再試行してよいとモデルに伝えるメッセージを会話に加えます。拒否自体を Claude Code が覆すわけではありません。フックが JSON を返さないかretry: falseを返すと、拒否は変わらず、モデルは元の拒否メッセージを受け取ります- 分類器がその操作について判定を出さなかったとき(応答が解析できなかった、または auto モードと別の安全チェックが分類器自身のリクエストを拒否した)、Claude Code は
retry: trueを無視します。そうした拒否では、あとで再試行するか先へ進むかを、Claude Code がすでに拒否メッセージでモデルに伝えています
Notification#
Claude Code が通知を送るときに動きます。通知の種類に一致します。matcher を省くと、すべての通知の種類でフックが動きます。デスクトップ通知をオフにしていても、これらのフックイベントは受け取ります(preferredNotifChannel 設定は notifications_disabled を含め、あなたへの知らせ方だけを変え、フックが動くかは変えません)。
| matcher | 発火するとき |
|---|---|
permission_prompt |
Claude が、ツールの使用か、サンドボックス内のコマンドのネットワークリクエストの承認を求めていて、その確認が約6秒待たれた |
idle_prompt |
Claude が応答を終えて約60秒たち、その後あなたが入力していない |
auth_success |
認証が完了した |
elicitation_dialog |
MCP サーバーが elicitation のフォームを開き、約6秒入力していない |
elicitation_url_dialog |
MCP サーバーがブラウザの URL を開くよう求め、約6秒入力していない |
elicitation_complete |
MCP サーバーが、URL モードの elicitation の完了を報告した |
elicitation_response |
MCP の elicitation の応答がサーバーへ送り返された |
agent_needs_input |
ターミナルでエージェントビューが開いている間に、バックグラウンドセッションがあなたの入力を待ち始めた。ターミナルセッションが、エージェントチームのチームメイトの端末設定の質問や、分類器のリクエスト課金についての auto モードの案内を出し、約6秒入力していないときも発火する |
agent_completed |
バックグラウンドセッションが完了または失敗した。ターミナルでエージェントビューが開いている間だけ発火する |
quota_auto_resume_fired |
claude.ai の利用上限で止まっていたタスクを、Claude Code が続けた:リセット時、または待っている間に Claude Code で行った操作(利用クレジットの追加・プランのアップグレード・モデルの切り替えなど)で使えるようになったときはそれより早く(モデル設定の例外あり) |
quota_auto_resume_stale |
コンピューターがスリープしている間に(約30分より長く)claude.ai の利用上限がリセットされた。Claude Code は続けずに、Enter が押されるのを待つ。それより短いスリープなら続けて、代わりに quota_auto_resume_fired が発火する |
quota_auto_resume_disabled |
Claude Code が、タスクを続けずに、claude.ai の利用上限の待ちを終えた:autoContinueAtUsageLimit がオフ、Claude Code が自分で始めた待ちの間にリセットが24時間より先に動いた、続けたタスクが上限に当たり続けた、または続行がモデルに届く前にブロックされた。Esc や Ctrl+C を押したとき、または「Don't continue automatically」を選んだときは発火しない |
quota_auto_resume_fired・quota_auto_resume_stale・quota_auto_resume_disabledの種類は v2.1.234 以降が必要です- ターミナルセッションで、サンドボックス内のコマンドのネットワークリクエストの
permission_promptは v2.1.246 以降が必要です - チームメイトの端末設定の質問の
agent_needs_inputは v2.1.248 以降が必要です
補足
permission_prompt・idle_prompt・elicitation_dialog・elicitation_url_dialog の種類は、タイミングをデスクトップ通知と共有するので、ターミナルセッションでは、ターミナルから離れていると見えるときだけ届きます。
permission_prompt:約6秒入力していないと届く。タイマーは権限確認が現れたときに始まり、キー入力のたびに延期される。Claude がツールの使用の許可を求めた瞬間にフックを動かすには、代わりにPermissionRequestを使うidle_prompt:Claude が応答を終えて約60秒後、その後入力していなくて、バックグラウンドのエージェント(バックグラウンドのサブエージェントなど)が動いていない場合だけ届く。Claude Code は、claude.ai の利用上限のリセットを待っている間はidle_promptを送らない。待ちが自然に終わると、代わりにquota_auto_resume_*の種類のどれかが発火するelicitation_dialog(elicitation のフォーム)とelicitation_url_dialog(ブラウザの URL のリクエスト):約6秒入力していないと届く。どちらもpermission_promptと同じ6秒のゲートを共有し、タイマーはダイアログが現れたときに始まり、キー入力のたびに延期される
別のダイアログが画面に出ている間に届いた権限リクエストや elicitation も、同じ6秒のゲートを、リクエストが届いたときから数えます。その通知は、リクエストが開いているダイアログの後ろで待っている間に届くことがあります。
Agent SDK の canUseTool コールバック(Claude Desktop と VS Code 拡張が Claude Code をホストする方法)へ権限リクエストを送るセッションでは、Claude Code は permission_prompt のタイミングが違います。
- Claude が権限を求めてから約6秒後に届く。入力している間も延期されない
- あなたか
PermissionRequestのフックが、それより早く答えると、Claude Code はpermission_promptを動かさない - これらのセッションで
permission_promptをオフにするには、CLAUDE_CODE_DISABLE_PERMISSION_PROMPT_NOTIFY_HOOKSを1にする - v2.1.233 より前は、これらのセッションで
permission_promptは発火しませんでした
通知の種類ごとに別のハンドラーを動かすには、別々の matcher を使います。次の設定は、Claude が権限の承認を求めたときに権限用の警告スクリプトを、Claude がしばらくアイドルのときに別の通知を動かします。
{
"hooks": {
"Notification": [
{
"matcher": "permission_prompt",
"hooks": [
{
"type": "command",
"command": "/path/to/permission-alert.sh"
}
]
},
{
"matcher": "idle_prompt",
"hooks": [
{
"type": "command",
"command": "/path/to/idle-notification.sh"
}
]
}
]
}
}
Notification の入力#
共通の入力フィールドに加えて、通知のテキストの message、任意の title、発火した種類を示す notification_type を受け取ります。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "Notification",
"message": "Claude needs your permission",
"title": "Permission needed",
"notification_type": "permission_prompt"
}
Notification のフックは通知をブロックも変更もできません。Claude Code は systemMessage と continue を捨てますが、terminalSequence は出します(フックの使い方のデスクトップ通知の例が頼っているのはこれです)。通知を外部のサービスへ転送するような、副作用のために使います。
SubagentStart#
Claude が Agent ツールでサブエージェントを起動したとき、Claude がサブエージェントを再開したとき、プロセス内のエージェントチームのチームメイトが新しいメッセージを処理するたびに動きます。エージェントの種類の名前で絞る matcher に対応します。組み込みのエージェントでは general-purpose・Explore・Plan などのエージェント名で、カスタムサブエージェントでは、ファイル名でなくエージェントの frontmatter の name フィールドです。
プラグインが提供するサブエージェントでは、エージェントの種類は、素の frontmatter の名前でなく、my-plugin:reviewer のようなプラグインのスコープ付きの識別子です。コロンがあるので、スコープ付きの名前は正規表現の経路に入り、完全一致させるには、^my-plugin:reviewer$ のように ^ と $ で固定します。
SubagentStart の入力#
共通の入力フィールドに加えて、サブエージェントの一意の識別子の agent_id と、matcher が絞るエージェント名の agent_type を受け取ります。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "SubagentStart",
"agent_id": "agent-abc123",
"agent_type": "Explore"
}
SubagentStart のフックはサブエージェントの作成をブロックできませんが、サブエージェントに文脈を注入できます。全フックが使える JSON 出力のフィールドに加えて、次を返せます。
| フィールド | 内容 |
|---|---|
additionalContext |
サブエージェントの会話の始まり、最初のプロンプトの前に、サブエージェントの文脈へ加わる文字列 |
{
"hookSpecificOutput": {
"hookEventName": "SubagentStart",
"additionalContext": "Follow security guidelines for this task"
}
}
同じサブエージェントでフックが再び動くとき、Claude Code は、サブエージェントの文脈に前の実行のコピーがまだ無い場合に限り、返された文脈を注入します。起動時に注入されたコピーはそのまま残るので、サブエージェントのプロンプトキャッシュが保たれます。自動コンパクションがそのコピーを捨てた後は、Claude Code が次の実行の文脈を再び注入します。
SubagentStop#
Claude Code のサブエージェントが応答を終えたときに動きます。エージェントの種類に一致します(値は SubagentStart と同じ)。
SubagentStop の入力#
共通の入力フィールドに加えて、stop_hook_active・agent_id・agent_type・agent_transcript_path・last_assistant_message を受け取ります。
agent_typeは matcher の絞り込みに使う値ですtranscript_pathはメインセッションの記録で、agent_transcript_pathは、入れ子のsubagents/フォルダに保存されるサブエージェント自身の記録ですlast_assistant_messageはサブエージェントの最後の応答のテキストなので、フックは記録ファイルを解析せずに使えます- すべての SubagentStop イベントが、Claude が起動したサブエージェントから来るわけではありません。Claude Code は、プロンプトの提案や
/btwの脇の質問など自身の機能のために内部のエージェントも動かし、それらが終わったときも SubagentStop が発火します。そうしたイベントのagent_typeは、セッション自身が動くエージェントの名前(--agentやagent設定で指定したもの)で、無ければ空文字列です - エージェントの種類を名指しする
matcherは、空のagent_typeには一致しません。matcher が省略・""・"*"のフックや、空文字列に一致する正規表現のフックは、agent_typeが空のイベントでも動きます - v2.1.271 以降では、
SubagentHandbackツールで動くサブエージェントは、止まる前にそのツールで報告を届けます。そのときlast_assistant_messageには、サブエージェントの締めのテキスト(あれば)が入り、それは届けられた報告ではありません。報告は、その呼び出しのmessage入力で、SubagentHandbackに一致するPreToolUseかPostToolUseのフックがtool_input.messageとして受け取ります - SubagentStop のフックは、
Stopの入力の節に述べるbackground_tasksとsession_cronsの配列も受け取ります。どちらの配列も、サブエージェントでなく親セッションの範囲です
{
"session_id": "abc123",
"transcript_path": "~/.claude/projects/.../abc123.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "SubagentStop",
"stop_hook_active": false,
"agent_id": "def456",
"agent_type": "Explore",
"agent_transcript_path": "~/.claude/projects/.../abc123/subagents/agent-def456.jsonl",
"last_assistant_message": "Analysis complete. Found 3 potential issues...",
"background_tasks": [],
"session_crons": []
}
SubagentStop のフックは、Stop のフックと同じ決定の制御の形式を使います。サブエージェントを動かし続けるエラーでないフィードバックのために、hookEventName を "SubagentStop" にした hookSpecificOutput.additionalContext も含みます。reason つきの decision: "block" を返すと、サブエージェントを動かし続け、reason をサブエージェントの次の指示として届けます。終了コード 2 でブロックするフックも、stderr のメッセージを同じように届けます。サブエージェントが戻った後に親セッションへ文脈を注入するには、代わりに Agent ツールへの PostToolUse のフックを使います。
TaskCreated#
TaskCreate ツールでタスクが作られようとしているときに動きます。命名規則の強制・タスクの説明の必須化・特定のタスクの作成の防止に使います。Task ツールの無いセッションでは、このイベントは発火しません。TaskCreated のフックは matcher に対応せず、発生のたびに発火します。
TaskCreated の入力#
共通の入力フィールドに加えて、task_id・task_subject、任意の task_description・teammate_name・team_name を受け取ります。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "TaskCreated",
"task_id": "task-001",
"task_subject": "Implement user authentication",
"task_description": "Add login and signup endpoints",
"teammate_name": "implementer",
"team_name": "session-a1b2c3d4"
}
| フィールド | 内容 |
|---|---|
task_id |
作成されるタスクの識別子 |
task_subject |
タスクのタイトル |
task_description |
タスクの詳しい説明。無いことがある |
teammate_name |
タスクを作るチームメイトの名前。無いことがある |
team_name |
非推奨。セッションから導いたチーム名で、将来のリリースで削除される |
TaskCreated の決定の制御#
作成を、2通りでブロックできます。どちらでも、Claude Code はタスクを削除し、あなたのメッセージをツールのエラーとして Claude に返します。このイベントの continue: false は無視され、Claude は作業を続けます。
- 終了コード 2:Claude Code は stderr のテキストをメッセージとして返す
- JSON
{"decision": "block", "reason": "..."}:Claude Code はreasonをメッセージとして返す
題が必要な形式に従わないタスクをブロックする例:
#!/bin/bash
INPUT=$(cat)
TASK_SUBJECT=$(echo "$INPUT" | jq -r '.task_subject')
if [[ ! "$TASK_SUBJECT" =~ ^\[TICKET-[0-9]+\] ]]; then
echo "Task subject must start with a ticket number, e.g. '[TICKET-123] Add feature'" >&2
exit 2
fi
exit 0
TaskCompleted#
タスクが完了としてマークされようとしているときに動きます。2つの場面で発火します:どのエージェントかが TaskUpdate ツールでタスクを明示的に完了にしたとき、またはエージェントチームのチームメイトが、進行中のタスクを持ったままターンを終えたとき。タスクを閉じる前に、テストや lint の通過のような完了の基準を強制するのに使います。TaskCompleted のフックは matcher に対応せず、発生のたびに発火します。
TaskCompleted の入力#
共通の入力フィールドに加えて、task_id・task_subject、任意の task_description・teammate_name・team_name を受け取ります。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "TaskCompleted",
"task_id": "task-001",
"task_subject": "Implement user authentication",
"task_description": "Add login and signup endpoints",
"teammate_name": "implementer",
"team_name": "session-a1b2c3d4"
}
| フィールド | 内容 |
|---|---|
task_id |
完了するタスクの識別子 |
task_subject |
タスクのタイトル |
task_description |
タスクの詳しい説明。無いことがある |
teammate_name |
タスクを完了するチームメイトの名前。無いことがある |
team_name |
非推奨。セッションから導いたチーム名で、将来のリリースで削除される |
TaskCompleted の決定の制御#
タスクの完了を制御する方法は2つあります。
- 終了コード 2:タスクは完了としてマークされず、stderr のメッセージがフィードバックとしてモデルに戻される
- JSON
{"continue": false, "stopReason": "..."}:チームメイトがターンを終えたことがイベントの引き金のときは、Stopのフックと同じように、チームメイトを完全に止める。stopReasonはユーザーに出る。TaskUpdateツールが引き金のときは、Claude Code はcontinue: falseを無視する。終了コード 2 は、それでも完了をブロックする
テストを動かし、失敗したらタスクの完了をブロックする例:
#!/bin/bash
INPUT=$(cat)
TASK_SUBJECT=$(echo "$INPUT" | jq -r '.task_subject')
# Run the test suite
if ! npm test 2>&1; then
echo "Tests not passing. Fix failing tests before completing: $TASK_SUBJECT" >&2
exit 2
fi
exit 0
Stop#
メインの Claude Code エージェントが応答を終えたときに動きます。ユーザーの割り込みで止まった場合は動きません。API エラーでは、代わりに StopFailure が発火します。
ヒント
/goal コマンドは、セッション単位のプロンプト型 Stop フックの組み込みのショートカットです。フックの設定を書かずに、ある条件に向かって Claude に作業を続けさせたいときに使います(ゴールを決めて任せる参照)。
Stop の入力#
共通の入力フィールドに加えて、stop_hook_active・last_assistant_message・background_tasks・session_crons を受け取ります。
stop_hook_activeは、Claude Code が、すでに Stop フックの結果として続行しているときtrueです。解決しない条件でブロックし続けないよう、この値を確かめるか記録を処理します。
Claude Code は、連続8回の続行の上限を適用します:Stop フックがターンを8回続けて続行させると、Claude Code は次のブロックを上書きしてターンを終えます。続けて続行した回数は、Claude がツールを呼ぶたびにリセットされます。上限を上げるには CLAUDE_CODE_STOP_HOOK_BLOCK_CAP を設定します
last_assistant_messageは Claude の最後の応答のテキストなので、フックは記録ファイルを解析せずに使えます。読み上げや通知のフックのように、いま完了したターンに作用するフックは、transcript_pathを読まずにこのフィールドを使います(すべての版で、Stop の時点で記録ファイルに最後のメッセージが入っているとは限らないため)background_tasksとsession_cronsの配列は、フックが「セッションが終わった」と「バックグラウンドの作業に起こされるのを待って一時停止している」を区別できるようにします。どちらの配列も、タスクのレジストリに届くときは存在し、何も実行中・予定されていなければ空です
background_tasks の各エントリは、実行中の1つのタスクを説明し、次のフィールドを使います。
| フィールド | 内容 |
|---|---|
id |
タスクの識別子 |
type |
shell・subagent・monitor・workflow・teammate・cloud session・MCP task などの、分かりやすいタスクの種類のラベル。各ラベルは、そのタスクを作った Claude Code の機能を示す。認識できない種類では、元の識別子にフォールバックする |
status |
現在のタスクの状態 |
description |
自由形式の説明。1000 文字が上限で、切り詰めたときは文字列内に … [+N chars] の印が入る |
command |
シェルのコマンドライン。1000 文字が上限。shell のタスクにだけ存在する |
agent_type |
サブエージェントの種類の名前。subagent のタスクにだけ存在する |
server |
MCP サーバー名。monitor と MCP task のタスクにだけ存在する |
tool |
MCP ツール名。monitor と MCP task のタスクにだけ存在する |
name |
ワークフロー名。workflow のタスクにだけ存在する |
session_crons の各エントリは、CronCreate・ScheduleWakeup・/loop を元にした、セッションの範囲の予定された起こしを1つ説明します。
| フィールド | 内容 |
|---|---|
id |
cron のタスクの識別子 |
schedule |
cron 式(例:0 9 * * 1-5) |
recurring |
1回限りの起こし(スケジュールが1つの発火時刻を表す)は false、一致するたびに再び発火するタスクは true |
prompt |
cron が発火したときに送信されるプロンプト。1000 文字が上限で、同じ … [+N chars] の印が付く |
実行中のシェルタスク1つと定期的な cron 1つを持つ Stop の入力の例:
{
"session_id": "abc123",
"transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "Stop",
"stop_hook_active": true,
"last_assistant_message": "I've completed the refactoring. Here's a summary...",
"background_tasks": [
{
"id": "task-001",
"type": "shell",
"status": "running",
"description": "tail logs",
"command": "tail -f /var/log/syslog"
}
],
"session_crons": [
{
"id": "cron-001",
"schedule": "0 9 * * 1-5",
"recurring": true,
"prompt": "check the build"
}
]
}
Stop の決定の制御#
Stop と SubagentStop のフックは、Claude が続行するかを制御できます。全フックが使える JSON 出力のフィールドに加えて、次のイベント固有のフィールドを返せます。
| フィールド | 内容 |
|---|---|
decision |
"block" で、Claude が止まるのを防ぐ。省くと Claude は止まれる |
reason |
decision が "block" のとき必須。Claude が続けるべき理由を伝える |
hookSpecificOutput.additionalContext |
Claude へのエラーでないフィードバック。会話は、Claude がそれに反応できるよう続くが、decision: "block" と違い、フックのエラーでなくフックのフィードバックとして記録に出る |
終了コード 2 でブロックするフックも reason と同じ経路をたどり、Claude は stderr のメッセージを、続けるべき理由の説明として受け取ります。
{
"decision": "block",
"reason": "Must be provided when Claude is blocked from stopping"
}
フックが設計どおりに動いて Claude に指針を出すとき(「終える前にテストスイートを実行して」など)は、additionalContext を使います。decision: "block" と同じループの保護(stop_hook_active の入力と、連続8回の続行の上限)で会話を続けますが、記録には Stop hook feedback というラベルが付き、フックのエラーの通知は出ません。
{
"hookSpecificOutput": {
"hookEventName": "Stop",
"additionalContext": "Please run the test suite before finishing"
}
}
StopFailure#
API エラーでターンが終わったとき、Stop の代わりに動きます。Claude Code は、terminalSequence を除き、フックの出力と終了コードを無視します。レート制限・認証の問題・ほかの API エラーで Claude が応答を完了できないときに、失敗の記録・警告の送信・復旧の操作に使います。
StopFailure の入力#
共通の入力フィールドに加えて、error、任意の error_details、任意の last_assistant_message を受け取ります。error フィールドがエラーの種類を示し、matcher の絞り込みに使われます。
| フィールド | 内容 |
|---|---|
error |
エラーの種類:rate_limit・overloaded・authentication_failed・oauth_org_not_allowed・account_on_hold・billing_error・invalid_request・model_not_found・server_error・max_output_tokens・cloud_credential_error・unknown |
error_details |
あれば、エラーの追加の詳細 |
last_assistant_message |
会話に出た、描画されたエラーのテキスト。このフィールドが Claude の会話の出力を持つ Stop と SubagentStop と違い、StopFailure では、"API Error: Rate limit reached" のような API のエラー文字列そのもの |
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "StopFailure",
"error": "rate_limit",
"error_details": "429 Too Many Requests",
"last_assistant_message": "API Error: Rate limit reached"
}
決定の制御はなく、通知と記録のためだけに動きます。
TeammateIdle#
エージェントチームのチームメイトが、ターンを終えた後にアイドルになろうとしているときに動きます。lint の通過を求める、出力ファイルが存在するか確かめるなど、チームメイトが作業をやめる前の品質ゲートの強制に使います。TeammateIdle のフックは matcher に対応せず、発生のたびに発火します。
TeammateIdle の入力#
共通の入力フィールドに加えて、teammate_name と team_name を受け取ります。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"permission_mode": "default",
"hook_event_name": "TeammateIdle",
"teammate_name": "researcher",
"team_name": "session-a1b2c3d4"
}
| フィールド | 内容 |
|---|---|
teammate_name |
アイドルになろうとしているチームメイトの名前 |
team_name |
非推奨。セッションから導いたチーム名で、将来のリリースで削除される |
TeammateIdle の決定の制御#
チームメイトの挙動を制御する方法は2つあります。
- 終了コード 2:チームメイトは stderr のメッセージをフィードバックとして受け取り、アイドルにならず作業を続ける
- JSON
{"continue": false, "stopReason": "..."}:Stopのフックと同じように、チームメイトを完全に止める。stopReasonはユーザーに出る
チームメイトがアイドルになるのを許す前に、ビルドの成果物があるか確かめる例:
#!/bin/bash
if [ ! -f "./dist/output.js" ]; then
echo "Build artifact missing. Run the build before stopping." >&2
exit 2
fi
exit 0
ConfigChange#
セッション中に設定ファイルが変わったときに動きます。設定変更の監査・セキュリティ方針の強制・設定ファイルへの許可されない変更のブロックに使います。
- Claude Code は、設定ファイル・管理ポリシーのファイル・スキルのファイルが変わったとき ConfigChange のフックを動かします。管理ポリシーでは、
managed-settings.jsonかmanaged-settings.d/のファイルが変わったときだけ動かします。サーバー管理の設定と、macOS の管理された環境設定や Windows のレジストリポリシーの変更は、フックを動かさずに適用します。wslInheritsWindowsSettingsを使う WSL では、変わった Windows 側の管理設定ファイルも、ポリシーのポーリングでフックを動かさずに適用します
matcher は設定の出どころで絞ります。
| matcher | 発火するとき |
|---|---|
user_settings |
~/.claude/settings.json が変わる |
project_settings |
.claude/settings.json が変わる |
local_settings |
.claude/settings.local.json が変わる |
policy_settings |
managed-settings.json か managed-settings.d/ のファイルが変わる |
skills |
.claude/skills/ のスキルのファイルが変わる |
セキュリティの監査のために、すべての設定変更を記録する例:
{
"hooks": {
"ConfigChange": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/audit-config-change.sh",
"args": []
}
]
}
]
}
}
ConfigChange の入力#
共通の入力フィールドに加えて、source と任意の file_path を受け取ります。source はどの設定の種類が変わったかを示し、file_path は変更された特定のファイルのパスです。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "ConfigChange",
"source": "project_settings",
"file_path": "/Users/.../my-project/.claude/settings.json"
}
ConfigChange の決定の制御#
設定の変更が有効になるのをブロックできます。変更を防ぐには、終了コード 2 か JSON の decision を使います。ブロックされると、新しい設定は実行中のセッションに適用されません。
| フィールド | 内容 |
|---|---|
decision |
"block" で、設定変更の適用を防ぐ。省くと変更は許される |
reason |
受け付けられるが、表示されることはない |
{
"decision": "block",
"reason": "Configuration changes to project settings require admin approval"
}
policy_settingsの変更はブロックできません。マシン上の管理設定ファイルが変わると、policy_settingsの出どころでもフックは発火するので、その編集の記録に使えますが、ブロックの判定は無視されます。これで、エンタープライズが管理する設定が常に有効になります。サーバー管理の設定が届いたり更新されたりしても、Claude Code はConfigChangeのフックを動かしません- Claude Code は ConfigChange のフックの JSON 出力のブロックの判定に従い、
systemMessageとcontinueは捨てます。reasonでブロックしても、終了コード 2 の stderr でブロックしても、ブロックされた変更は、あなたにも Claude にもメッセージを出しません。Claude Code はデバッグログに1行書くだけです
CwdChanged#
メインの会話のシェルコマンドが作業ディレクトリを変えたとき(Claude が cd を実行したときなど)に動きます。ディレクトリの変化への反応:環境変数の再読み込み・プロジェクト固有のツールチェーンの有効化・セットアップスクリプトの自動実行に使います。ディレクトリごとの環境を管理する direnv のようなツール向けに、FileChanged と組み合わせて使います。
- CwdChanged のフックは
CLAUDE_ENV_FILEを使えます。そのファイルに書いた変数は、次の CwdChanged イベントで Claude Code が消すまで、後続の Bash コマンドに残ります - CwdChanged は matcher に対応せず、発生のたびに発火します
CwdChanged の入力#
共通の入力フィールドに加えて、old_cwd と new_cwd を受け取ります。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
"cwd": "/Users/my-project/src",
"hook_event_name": "CwdChanged",
"old_cwd": "/Users/my-project",
"new_cwd": "/Users/my-project/src"
}
CwdChanged の出力#
全フックが使える JSON 出力のフィールドに加えて、FileChanged が監視するファイルパスを動的に設定する watchPaths を返せます。
| フィールド | 内容 |
|---|---|
watchPaths |
絶対パスの配列。現在の動的な監視リストを置き換える。matcher の設定にあるパスは常に監視される。空の配列を返すと動的なリストが消える(新しいディレクトリに入ったときの典型) |
決定の制御はなく、ディレクトリの変更をブロックできません。Claude Code は JSON 出力から watchPaths と systemMessage を読み、continue は捨てます。対話セッションでは、systemMessage を短いターミナル通知として表示します。メッセージは SDK のメッセージストリームには届きません。
DirectoryAdded#
セッションの途中で /add-dir コマンドで作業ディレクトリを追加した後、または SDK クライアントが register_repo_root の制御リクエストで追加した後に動きます。新しく追加したリポジトリの準備(依存関係のインストールなど)に使います。
次の場合、Claude Code はこのイベントを発火しません。
--add-dir起動フラグでディレクトリを渡したとき(そのディレクトリは SessionStart が扱う)/permissionsの Workspace タブでディレクトリを追加したとき- すでに作業ディレクトリである、またはその中にあるディレクトリを追加したとき
Claude Code は、サンドボックスと権限の状態を更新した後に DirectoryAdded を発火するので、サンドボックス内のツールは、フックが動く時点で新しいディレクトリをすでに見られます。フックのコマンド自体は、サンドボックスの外で動きます。Claude Code はフックを待ちません:追加はすぐ完了し、フックは既定の 600 秒のタイムアウトでバックグラウンドで動きます。matcher はディレクトリが追加された方法で絞ります。
| matcher | 発火するとき |
|---|---|
slash_command |
/add-dir でディレクトリを追加した |
register_repo_root |
SDK クライアントが register_repo_root の制御リクエストでディレクトリを追加した |
DirectoryAdded の入力#
共通の入力フィールドに加えて、directory と source を受け取ります。
| フィールド | 内容 |
|---|---|
directory |
追加されたディレクトリの絶対パス |
source |
ディレクトリが追加された方法。/add-dir なら "slash_command"、SDK の制御リクエストなら "register_repo_root" |
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
"cwd": "/Users/my-project",
"hook_event_name": "DirectoryAdded",
"directory": "/Users/my-other-repo",
"source": "slash_command"
}
決定の制御はなく、追加はフックが動く時点ですでに完了しているのでブロックできません。Claude Code は JSON 出力の continue を捨て、残りは出どころで扱いが変わります。
slash_command:Claude Code は、フックのsystemMessageを、あなたに見せず、次の会話のターンで Claude に文脈として届ける。失敗したフックの数が記録に出て、失敗の全出力はデバッグログへ行くregister_repo_root:Claude Code は、systemMessageの出力と失敗の出力をデバッグログにだけ書く
FileChanged#
監視しているファイルがディスク上で変わったときに動きます。Claude Code は、ツール呼び出しを調べるのではなく、ファイルシステムの監視で変更を検知するので、何がファイルを変えたかにかかわらずフックを動かします:Edit や Write のツール呼び出し・Claude が Bash で動かすスクリプト・Claude Code の外のプロセス。よくある用途は、プロジェクトの設定ファイルが変わったときの環境変数の再読み込みです。
このイベントの matcher は2つの役割を持ちます。
- 監視リストを作る:値は
|で分割され、各部分が作業ディレクトリ内のリテラルのファイル名として登録されます。".envrc|.env"ならちょうどその2ファイルを監視します。正規表現は、ここでは役に立ちません。^\.envのような値は、文字どおり^\.envという名前のファイルを監視します - 動くフックを絞る:監視ファイルが変わったとき、同じ値が、変更されたファイルのベース名に対する標準の matcher の規則で、どのフックグループが動くかを絞ります
data.csv が(Bash コマンドや外部のスクリプトが書き換えた場合を含め)変わった後に、改行コードを正規化する例:
{
"hooks": {
"FileChanged": [
{
"matcher": "data.csv",
"hooks": [
{
"type": "command",
"command": "/path/to/normalize-line-endings.sh"
}
]
}
]
}
}
フックは、stdin の JSON 入力の file_path フィールドから、変更されたファイルの絶対パスを読みます。grep の確認は、perl が取り除くものと同じ、行末の CR を調べるので、正規化の後の実行はファイルに触れずに終わります。もっと緩い確認だと無限ループになります(perl -i は何も置換しなくてもファイルを書き換え、Claude Code は書き換えのたびにフックを再び動かすため)。このスクリプトを /path/to/normalize-line-endings.sh に保存し、実行権限を付けます。
#!/bin/bash
FILE=$(jq -r .file_path)
if grep -q $'\r$' "$FILE"; then
perl -pi -e 's/\r$//' "$FILE"
fi
確かめるには、Bash コマンドで data.csv に CRLF の行を追記するよう Claude に頼みます。Claude Code がフックを動かし、ファイルは LF の改行で終わります。
- あらかじめ名前を挙げられないファイルを監視するには、フックから
watchPathsを返して、監視リストを動的に更新します。Claude Code は、何かが監視するファイルを挙げたときだけ監視を始めるので、リストの初期値は、少なくとも1つのファイルを matcher で挙げる FileChanged のグループか、watchPathsを返す SessionStart や CwdChanged のフックで与えます - matcher は、ファイルが変わったときにどのフックグループが動くかを、引き続き絞ります。動的なパスを扱うグループには、監視リストに何も足さずに、監視するすべてのファイルに一致する、省略した matcher を付けます。
"*"の matcher もすべてのファイルに一致しますが、Claude Code は、ほかの値と同じように、これを監視リストに、*という名前のリテラルのファイルとして登録します - FileChanged のフックは
CLAUDE_ENV_FILEを使えます。そのファイルに書いた変数は、次の CwdChanged イベントで Claude Code が消すまで、後続の Bash コマンドに残ります
FileChanged の入力#
共通の入力フィールドに加えて、file_path と event を受け取ります。
| フィールド | 内容 |
|---|---|
file_path |
変更されたファイルの絶対パス |
event |
何が起きたか:変更されたファイルは "change"、作成されたファイルは "add"、削除されたファイルは "unlink" |
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
"cwd": "/Users/my-project",
"hook_event_name": "FileChanged",
"file_path": "/Users/my-project/.envrc",
"event": "change"
}
FileChanged の出力#
全フックが使える JSON 出力のフィールドに加えて、監視するファイルパスを動的に更新する watchPaths を返せます。
| フィールド | 内容 |
|---|---|
watchPaths |
絶対パスの配列。現在の動的な監視リストを置き換える。matcher の設定にあるパスは常に監視される。フックのスクリプトが、変更されたファイルに基づいて、監視するファイルをさらに見つけるときに使う |
決定の制御はなく、ファイルの変更が起きるのをブロックできません。Claude Code は JSON 出力から watchPaths と systemMessage を読み、continue は捨てます。対話セッションでは、systemMessage を短いターミナル通知として表示します。メッセージは SDK のメッセージストリームには届きません。
WorktreeCreate#
claude --worktree・isolation: "worktree" を使うサブエージェント・Claude Code が自分の worktree に隔離するバックグラウンドセッションのどれからでも、worktree が作られようとしているときに動きます。既定では、Claude Code は git worktree で隔離された作業コピーを作ります。WorktreeCreate のフックを設定すると、その既定の git の動作が置き換わり、SVN・Perforce・Mercurial など別のバージョン管理システムを使えます。
- フックは既定の動作を完全に置き換えるので、
.worktreeincludeは処理されません。.envのようなローカルの設定ファイルを新しい worktree にコピーしたいなら、フックのスクリプトの中で行います - フックは、作成した worktree のディレクトリのパスを返す必要があります。Claude Code は、そのパスを隔離されたセッションの作業ディレクトリとして使います。各フックの種類がパスを返す方法は、WorktreeCreate の出力を参照してください
- Claude Code は、フックの成功と返されたパスに従い、
systemMessageとcontinueは捨てます
SVN の作業コピーを作り、Claude Code が使うパスを出力する例です。リポジトリの URL は自分のものに置き換えます。
{
"hooks": {
"WorktreeCreate": [
{
"hooks": [
{
"type": "command",
"command": "bash -c 'NAME=$(jq -r .name); DIR=\"$HOME/.claude/worktrees/$NAME\"; svn checkout https://svn.example.com/repo/trunk \"$DIR\" >&2 && echo \"$DIR\"'"
}
]
}
]
}
}
フックは、stdin の JSON 入力から worktree の name を読み、新しいディレクトリに新しいコピーをチェックアウトし、そのディレクトリのパスを出力します。最後の行の echo が、Claude Code が worktree のパスとして読むものです。パスの邪魔にならないよう、ほかの出力は stderr にリダイレクトします。
WorktreeCreate の入力#
共通の入力フィールドに加えて、name フィールドを受け取ります。ユーザーが指定したか自動生成された、新しい worktree のスラッグの識別子です(例:bold-oak-a3f2)。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "WorktreeCreate",
"name": "feature-auth"
}
WorktreeCreate の出力#
標準の許可・ブロックの決定モデルは使いません。代わりに、フックの成功か失敗が結果を決めます。フックは、作成した worktree のディレクトリのパスを返す必要があります。
-
コマンドフック(
type: "command"):stdout の、空でない最後の行としてパスを出力する。Claude Code は、その行を読む前に ANSI のエスケープコードを取り除くので、echoの前に出たシェルの起動バナーは無視される。ほかのフックの出力は stderr にリダイレクトする -
HTTP フック(
type: "http"):レスポンスボディに{ "hookSpecificOutput": { "hookEventName": "WorktreeCreate", "worktreePath": "/absolute/path" } }を返す -
フックが失敗するか、パスを出さないと、worktree の作成はエラーで失敗します
-
Claude Code は、相対パスを、フックが動いたディレクトリに対して解決し、その中の
.や..の部分をたたみます。結果のパスが Claude Code が入れるディレクトリでないと、セッションはそのパスを名指しするエラーを出し、終了コード 1 で終わります -
Claude Code は、
.や..の部分を含む絶対パスと、リポジトリルートより下のシンボリックリンクを通るパスを拒否します(リポジトリにコミットされたシンボリックリンクが、worktree をリポジトリの外へ向け直せるため)。エラーは拒否された部分を名指しします。正規化された、リポジトリ内のシンボリックリンクを通らないパスを返します。v2.1.216 より前は、worktree の作成は、この選別なしにフックのパスに従っていました
WorktreeRemove#
worktree が削除されようとしているときに動きます。WorktreeCreate の後始末の対です。次のときに発火します。
--worktreeのセッションを終了して、削除を選んだときisolation: "worktree"のサブエージェントが終わったとき- フックが作った worktree を持つバックグラウンドセッションを削除したとき
git の worktree では、Claude Code が git worktree remove で後始末を自動で行います。WorktreeCreate のフックを設定したなら、それが作った worktree の後始末を制御するため、WorktreeRemove のフックと組み合わせます。
-
WorktreeRemove のフックが無い:
--worktreeのセッションを終了して削除を選んだとき、Claude Code は、WorktreeCreate のフックが返したパスに対するgit worktree remove --forceにフォールバックするので、git が認識する worktree は削除される。git が認識しない worktree(git 以外のバージョン管理システムでフックが作ったものなど)は、ディスクに残る。フックが作った worktree を持つバックグラウンドセッションを削除したときの扱いは、エージェントビューの削除の規則を参照 -
フックが終了コード 0 で終わる:worktree は削除されたと数えられる。Claude Code はフックからほかに何も読まないので、フックがディレクトリを削除したことを確かめる
-
フックが非 0 で終わる:その後も
worktree_pathのディレクトリが存在すれば、削除は失敗し、worktree は git へのフォールバックなしでディスクに残る。非 0 で終わる前にディレクトリを削除したフックは、削除されたと数えられる -
Claude Code は、フックが作った worktree のブランチを、決して削除しません。Claude Code が知っているのは、WorktreeCreate のフックが返したパスだけだからです。WorktreeCreate のフックがブランチを作るなら、WorktreeRemove のフックでそれを削除します
-
Claude Code は、WorktreeRemove のフックの JSON 出力のフィールド(
systemMessageとcontinueなど)を捨てます -
バックグラウンドセッションの削除では、Claude Code は、フックを動かす前に、保存された worktree のパスを確かめ、シンボリックリンクであるパス、またはリポジトリルートより下でシンボリックリンクを通るパスを拒否します。まだファイルを含む worktree でフックが動くのは、エージェントビューで削除を確認したときだけです。そうした worktree では、
claude rmは、代わりにセッションと worktree を残します。v2.1.216 より前は、フックはこれらの確認なしに保存されたパスで動いていました
Claude Code は、WorktreeCreate が返したパスを、フックの入力に worktree_path として渡します。そのパスを読んでディレクトリを削除する例:
{
"hooks": {
"WorktreeRemove": [
{
"hooks": [
{
"type": "command",
"command": "bash -c 'jq -r .worktree_path | xargs rm -rf'"
}
]
}
]
}
}
WorktreeRemove の入力#
共通の入力フィールドに加えて、削除される worktree の絶対パスの worktree_path フィールドを受け取ります。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "WorktreeRemove",
"worktree_path": "/Users/.../my-project/.claude/worktrees/feature-auth"
}
WorktreeRemove のフックの終了コードが結果を決めます。フックが非 0 で終わり、その後も worktree_path のディレクトリが存在すると、削除は失敗します。
- worktree はディスクに残り、フックのコマンドと stderr はデバッグログへ行く
- バックグラウンドセッションを削除していたなら、セッションも残る。エージェントビューの拒否メッセージが、フックがどう終わったか(
exited 1など)を報告し、stderr の最初の部分を引用して、もう一度セッションを削除すればディレクトリを削除できるかを述べる
PreCompact#
Claude Code がコンパクションを実行しようとする前に動きます。matcher の値は、コンパクションが手動か自動かを示します。
| matcher | 発火するとき |
|---|---|
manual |
/compact |
auto |
会話が自動コンパクトのウィンドウに達したときの自動コンパクト |
- コンパクションをブロックするには、終了コード 2 で終えます。手動の
/compactでは、stderr のメッセージがユーザーに出ます。"decision": "block"を持つ JSON を返してもブロックできます - 自動コンパクションのブロックの効果は、発火した時点で違います。コンテキストの上限の前に先回りして起きたコンパクションなら、Claude Code はそれを飛ばし、会話はコンパクトされないまま続きます。API がすでに返したコンテキスト上限のエラーから復旧するために起きたコンパクションなら、元のエラーが表に出て、現在のリクエストは失敗します
- Claude Code は、PreCompact のフックの
systemMessageとcontinueを捨てます
PreCompact の入力#
共通の入力フィールドに加えて、trigger と custom_instructions を受け取ります。manual では、custom_instructions はユーザーが /compact に渡した内容で、何も渡さなければ null です。auto では null です。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "PreCompact",
"trigger": "manual",
"custom_instructions": null
}
PostCompact#
Claude Code がコンパクションを完了した後に動きます。コンパクト後の新しい状態への反応(生成された要約の記録・外部の状態の更新など)に使います。Claude Code は、PostCompact のフックの systemMessage と continue を捨てます。PreCompact と同じ matcher の値が当てはまります。
| matcher | 発火するとき |
|---|---|
manual |
/compact の後 |
auto |
会話が自動コンパクトのウィンドウに達したときの自動コンパクトの後 |
PostCompact の入力#
共通の入力フィールドに加えて、trigger と compact_summary を受け取ります。compact_summary は、コンパクションが生成した会話の要約です。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "PostCompact",
"trigger": "manual",
"compact_summary": "Summary of the compacted conversation..."
}
決定の制御はありません。コンパクションの結果には影響せず、後続の作業を行えます。
PreModelSwitch#
あなたやクライアントが要求したモデルの切り替えを Claude Code が適用する前に動きます。切り替えのブロック・確認の要求・切り替えが起きる前にそのコストを見せることに使います。v2.1.251 以降が必要です。Claude Code は、次の要求で動かします。
/model <name>と/modelのピッカー- Option+P か Alt+P のモデルピッカー
/configの Model 設定- fast mode を有効にしてセッションのモデルが変わるとき
- Agent SDK のホストか Remote Control からの
set_modelリクエスト、またはapply_flag_settingsリクエストでのモデル変更
Claude Code は、自動のモデルフォールバックや、セッションを再開したときのモデルの復元のように、自分で行う切り替えでは PreModelSwitch のフックを動かしません。それらの変更は PostModelSwitch にだけ届きます。
- Claude Code は、matcher を、セッションの切り替え先のモデルの正規名と、
[1m]の接尾辞を無視して比べます。opusのようなエイリアス・日付つきのモデル ID・Amazon Bedrock のモデル ID のようなプロバイダー固有の ID は、解決される1つの正規名に一致するので、claude-opus-5は Opus 5 のあらゆる書き方をカバーします - 切り替え先の正規名を判定できないとき(自分の LLM ゲートウェイだけが知るカスタムのモデル ID など)、Claude Code は matcher に関係なく、すべての PreModelSwitch のフックを動かします。そのため、ブロックするフックは、matcher だけに頼らず、入力の
to_modelを確かめます - matcher は、完全な名前・
claude-opus-4-6|claude-opus-5のような|区切りの一覧・.*opus.*のような正規表現で書きます
完全な名前の matcher を使い、フックの入力の to_model も確かめて、Opus 4.6 への切り替えを終了コード 2 で拒否し、ほかの切り替えは通す例(macOS・Linux):
{
"hooks": {
"PreModelSwitch": [
{
"matcher": "claude-opus-4-6",
"hooks": [
{
"type": "command",
"command": "jq -e '.to_model | test(\"opus-4-6\")' > /dev/null && { echo 'Opus 4.6 is retired for this project. Use a newer model.' >&2; exit 2; }; exit 0"
}
]
}
]
}
}
Windows では、command を powershell.exe、args を -NoProfile・-ExecutionPolicy・Bypass・-File・${CLAUDE_PROJECT_DIR}/.claude/hooks/block-opus-46.ps1 にして、次のスクリプトを動かします。
$hookInput = [Console]::In.ReadToEnd() | ConvertFrom-Json
if ($hookInput.to_model -match 'opus-4-6') {
[Console]::Error.WriteLine('Opus 4.6 is retired for this project. Use a newer model.')
exit 2
}
exit 0
確かめるには、別のモデルで動いているセッションから /model claude-opus-4-6 を実行します。Claude Code は現在のモデルを保ち、PreModelSwitch のフックが切り替えをブロックしたことを、あなたのメッセージを理由に添えて報告します。
PreModelSwitch の入力#
共通の入力フィールドに加えて、次の表のフィールドを受け取ります。最後の5つは、新しいモデルへ会話を再送するコストを表すので、切り替えの前にその数字をフックが見せられます。
| フィールド | 型 | 内容 |
|---|---|---|
from_model |
string | 切り替え元のモデル ID |
to_model |
string | 切り替え先のモデル ID。matcher はこのモデルの正規名と比べる |
requested_model |
string か null |
要求が指名したモデル:opus のようなエイリアス・完全なモデル ID、既定のモデルへの要求なら null |
source |
string | 要求の出どころ:/model <name>・/config の Model 設定・fast mode の有効化は "command"、モデルピッカーは "picker"、Agent SDK のホストか Remote Control からの set_model リクエストや apply_flag_settings でのモデル変更は "sdk" |
context_tokens |
number | 次のリクエストがプロンプトとして再送するトークン数:メインの会話の最後の応答の、入力・キャッシュ読み取り・キャッシュ作成・出力のトークンの合計。最初の応答の前は 0 |
prompt_cache_warm |
boolean | 現在のモデルのプロンプトキャッシュがまだ温かい可能性が高いか(切り替えるとそれを失うことを意味する) |
cache_ttl |
string | このセッションで Claude Code が要求するプロンプトキャッシュの有効期間:"5m" か "1h" |
estimated_cache_write_usd |
number | to_model で cache_ttl の料金で context_tokens をプロンプトキャッシュへ書き込む推定コスト(米ドル。次の応答を除く)。サーバーが文脈全体を再キャッシュしなくてよいことがあるので、推定として扱う |
pricing |
string | Claude Code が estimated_cache_write_usd をどう価格づけたか:組織が自分の料金を設定していればその料金の "configured"、定価の "catalog"、to_model の価格が不明で Claude Code が既定の料金を仮定した場合の "default" |
Sonnet 5 で動くセッションでの /model opus の入力の例:
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "PreModelSwitch",
"from_model": "claude-sonnet-5",
"to_model": "claude-opus-5",
"requested_model": "opus",
"source": "command",
"context_tokens": 182340,
"prompt_cache_warm": true,
"cache_ttl": "5m",
"estimated_cache_write_usd": 1.1396,
"pricing": "catalog"
}
PreModelSwitch の決定の制御#
切り替えを取り消す・ユーザーに確認を求める・進めることができます。終了コード 2 か最上位の decision: "block" で、切り替えを取り消します。より細かく制御するには、PreToolUse と同じように、hookSpecificOutput オブジェクトに permissionDecision と permissionDecisionReason を返します。PreModelSwitch が受け付けるのは "allow"・"deny"・"ask" で、"defer"・updatedInput・additionalContext は受け付けません。
| フィールド | 内容 |
|---|---|
permissionDecision |
"allow" は進め、プロンプトキャッシュが温かい間に Claude Code が出す確認を省く。"deny" は切り替えを取り消す。"ask" はユーザーに確認を求める |
permissionDecisionReason |
"deny" では、切り替えがブロックされた理由としてユーザーに出るか、set_model リクエストのエラーとして返る。"ask" では、確認のプロンプトに出る。"allow" では無視される |
"ask"の確認を出せるのは、対話セッションの/modelだけです。-pフラグの非対話モード・/config・set_modelリクエストを含む、ほかのすべての場所で、Claude Code は"ask"を拒否として扱います- ユーザーに確認を求め、
context_tokensのトークン数を引用する例:
{
"hookSpecificOutput": {
"hookEventName": "PreModelSwitch",
"permissionDecision": "ask",
"permissionDecisionReason": "Switching now re-sends about 180k tokens to the new model. Continue?"
}
}
- 複数の PreModelSwitch のフックが異なる判定を返すときの優先順位は
deny>ask>allowです - Claude Code は、判定にかかわらず、フックが返した
systemMessageをユーザーに見せるので、コスト報告のフックは{"systemMessage": "..."}を返して終了コード 0 で終えられます - タイムアウトまでに応答しない PreModelSwitch のフックは、切り替えをブロックします(
PreToolUseでは逆に、タイムアウトしたコマンドフックはツール呼び出しを続けさせる)。このイベントの既定のタイムアウトは 30 秒です。PreModelSwitchが動かすのはcommand・http・mcp_toolのフックだけなので、promptとagentの既定は当てはまりません - 終了コードが 0 でも 2 でもなく、JSON の判定も出さないフックはブロックしません。Claude Code は stderr を表示し、切り替えを適用します(それ以外の終了コードの節を参照)
PostModelSwitch#
セッションのモデルが変わった後に動きます。CLAUDE.md をすべて編集せずに、モデルごとの指針を Claude に渡すのに使います(特定のモデルで適用される、組織全体の指示など)。v2.1.251 以降が必要です。モデルはすでに変わっているので、ブロックはできません。Claude Code は、次のどの変更の後でも PostModelSwitch のフックを動かします。
- あなたやクライアントが要求した切り替え
- セッションのモデルを変える、自動のモデルフォールバック
opusplanのような設定がプランモードに入る・出るとき- セッションを再開したときに Claude Code がモデルを復元するとき
フォールバックモデルのチェーンのモデルがターンを処理するときは、PostModelSwitch のフックを動かしません。その置き換えは1ターンだけで、セッションのモデルは変わらないためです。matcher は PreModelSwitch と同じ規則に従い、セッションが切り替わった先のモデルの正規名と比べます。セッションのモデルが Opus のどのモデルに変わったときも、指針を足す例:
{
"hooks": {
"PostModelSwitch": [
{
"matcher": ".*opus.*",
"hooks": [
{
"type": "command",
"command": "echo 'On Opus, delegate implementation work to subagents and keep this conversation for planning and review.'"
}
]
}
]
}
}
確かめるには、別のモデルで動いているセッションから Opus のモデルに切り替え(Sonnet のセッションで /model opus を実行するなど)、現在のモデルについてどんな指針があるか Claude に尋ねます。
PostModelSwitch の入力#
PreModelSwitch と同じフィールドを受け取りますが、hook_event_name は "PostModelSwitch" で、source の値が2つ加わります:自動のフォールバックなど、Claude Code が自分で行った変更は "auto"、セッションを再開したときに復元したモデルは "resume" です。source が "auto" のとき requested_model は null で、"resume" のときは Claude Code が復元した、保存されたモデル設定です。
PostModelSwitch の決定の制御#
Claude Code は、終了コード 0 でのフックのプレーンテキストの stdout か、JSON 出力の additionalContext を受け取り、切り替え後の次のリクエストで Claude に届けます。全フックが使える JSON 出力のフィールドに加えて、次を返せます。
| フィールド | 内容 |
|---|---|
additionalContext |
次のリクエストで Claude の文脈へ加わる文字列 |
次のプロンプトを送った後、5秒たってもフックが終わっていなければ、Claude Code は出力なしでそのリクエストを送り、出力はその次のリクエストに付けます。次のリクエストまでにモデルが何度か変わったときは、Claude Code は、最後の切り替えの切り替え先のモデルの出力だけを届けます。
SessionEnd#
Claude Code のセッションが終わるときに動きます。後始末・セッションの統計の記録・セッションの状態の保存に向きます。終了の理由で絞る matcher に対応します。フックの入力の reason フィールドが、セッションが終わった理由を示します。
| 理由 | 内容 |
|---|---|
clear |
/clear コマンドでセッションをクリアした |
resume |
対話式の /resume でセッションを切り替えた |
logout |
ユーザーがログアウトした |
prompt_input_exit |
プロンプト入力が見えている間にユーザーが終了した |
other |
その他の終了理由 |
bypass_permissions_disabled |
v2.1.234 で削除され、Claude Code は送らない。SessionEnd の matcher から外す |
SessionEnd の入力#
共通の入力フィールドに加えて、セッションが終わった理由を示す reason フィールドを受け取ります。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "SessionEnd",
"reason": "other"
}
決定の制御はなく、セッションの終了をブロックできませんが、後始末はできます。Claude Code は systemMessage などの JSON 出力のフィールドを捨てます。
SessionEnd のフックの既定のタイムアウトは 1.5 秒で、終了するとき・/clear を実行するとき・対話式の /resume でセッションを切り替えるときに適用されます。フックにもっと時間を与える方法は2つあります。
- フックごとの
timeout:そのフックの設定にtimeoutを設定する。全体の予算は、設定ファイルの中で最大のフックごとのtimeoutに合わせて自動で上がる(上限 60 秒)。この方法で予算を上げても、自分のtimeoutを持たないフックは、既定のままになる。プラグインが提供するフックに設定したタイムアウトは、予算を上げない CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS:この環境変数をミリ秒で設定して、予算を明示的に上書きする。設定した値は、自分のtimeoutを持たない各フックのタイムアウトにもなる
予算を 5 秒にする例:
CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS=5000 claude
v2.1.268 より前は、CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS が上げるのは全体の予算だけで、自分の timeout を持たないフックは、それでも 1.5 秒後に取り消されていました。
Elicitation#
MCP サーバーがタスクの途中でユーザーの入力を求めたときに動きます。既定では、Claude Code がユーザーの応答用の対話ダイアログを出します。フックはこの要求を横取りして、ダイアログを完全に飛ばし、プログラムで応答できます。matcher は MCP サーバー名に一致します。
Elicitation の入力#
共通の入力フィールドに加えて、mcp_server_name と message、任意の mode・url・elicitation_id・requested_schema を受け取ります。最もよくある、フォームモードの elicitation:
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "Elicitation",
"mcp_server_name": "my-mcp-server",
"message": "Please provide your credentials",
"mode": "form",
"requested_schema": {
"type": "object",
"properties": {
"username": { "type": "string", "title": "Username" }
}
}
}
ブラウザでの認証に使う、URL モードの elicitation:
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "Elicitation",
"mcp_server_name": "my-mcp-server",
"message": "Please authenticate",
"mode": "url",
"url": "https://auth.example.com/login"
}
Elicitation の出力#
ダイアログを出さずにプログラムで応答するには、hookSpecificOutput を持つ JSON オブジェクトを返します。
{
"hookSpecificOutput": {
"hookEventName": "Elicitation",
"action": "accept",
"content": {
"username": "alice"
}
}
}
| フィールド | 値 | 内容 |
|---|---|---|
action |
accept・decline・cancel |
リクエストを受け入れる・断る・キャンセルする |
content |
object | 送信するフォームのフィールドの値。action が accept のときだけ使われる |
終了コード 2 は elicitation を拒否します。Claude Code は stderr のメッセージをどこにも表示しません。Claude Code は Elicitation のフックの JSON 出力の hookSpecificOutput に従い、systemMessage と continue は捨てます。
ElicitationResult#
ユーザーが MCP の elicitation に応答した後に動きます。フックは、応答が MCP サーバーへ送り返される前に、それを観察・変更・ブロックできます。matcher は MCP サーバー名に一致します。
ElicitationResult の入力#
共通の入力フィールドに加えて、mcp_server_name と action、任意の mode・elicitation_id・content を受け取ります。
{
"session_id": "abc123",
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"cwd": "/Users/...",
"hook_event_name": "ElicitationResult",
"mcp_server_name": "my-mcp-server",
"action": "accept",
"content": { "username": "alice" },
"mode": "form",
"elicitation_id": "elicit-123"
}
ElicitationResult の出力#
ユーザーの応答を上書きするには、hookSpecificOutput を持つ JSON オブジェクトを返します。
{
"hookSpecificOutput": {
"hookEventName": "ElicitationResult",
"action": "decline",
"content": {}
}
}
| フィールド | 値 | 内容 |
|---|---|---|
action |
accept・decline・cancel |
ユーザーのアクションを上書きする |
content |
object | フォームのフィールドの値を上書きする。action が accept のときだけ意味がある |
終了コード 2 は応答をブロックし、実際のアクションを decline に変えます。Claude Code は stderr のメッセージをどこにも表示しません。Claude Code は ElicitationResult のフックの JSON 出力の hookSpecificOutput に従い、systemMessage と continue は捨てます。
イベントごとの、使えるハンドラーの種類#
コマンド・HTTP・MCP ツールのフックのほかに、LLM が操作を許可するかブロックするかを評価するプロンプト型フック(type: "prompt")と、ツールを使える、エージェント的な検証者を起動するエージェント型フック(type: "agent")があります。すべてのイベントがすべてのフックの種類に対応するわけではありません。
5種類(command・http・mcp_tool・prompt・agent)すべてに対応するイベント:
| 対応 | イベント |
|---|---|
| 5種類すべて | PermissionDenied・PostToolBatch・PostToolUse・PostToolUseFailure・PreToolUse・Stop・SubagentStop・TaskCompleted・TaskCreated・TeammateIdle・UserPromptExpansion・UserPromptSubmit |
command・http・mcp_tool・prompt(agent は不可) |
PermissionRequest |
command・http・mcp_tool(prompt と agent は不可) |
ConfigChange・CwdChanged・DirectoryAdded・Elicitation・ElicitationResult・FileChanged・InstructionsLoaded・MessageDisplay・Notification・PostCompact・PostModelSwitch・PreCompact・PreModelSwitch・SessionEnd・StopFailure・SubagentStart・WorktreeCreate・WorktreeRemove |
command と mcp_tool(http・prompt・agent は不可) |
SessionStart・Setup |
PermissionRequestにエージェント型フックを設定しても、Claude Code はそれを飛ばし、権限の流れは変わらず進みます。フックから許可や拒否をするには、コマンドか HTTP のフックから決定オブジェクト(PermissionRequestの決定の制御)を返しますSessionStartとSetupのmcp_toolフックがいつ動くかは、MCP ツールフックのフィールドの節に書いてあります
プロンプト型フック#
コマンドを実行する代わりに、プロンプト型フックは次のように動きます。
- フックの入力とあなたのプロンプトを Claude のモデルに送る(既定は、Claude Code がバックグラウンドの機能に使うモデル)
- LLM が、判定を含む構造化された JSON で応答する
- Claude Code が判定を自動で処理する
プロンプト型フックの設定#
type を "prompt" にし、command の代わりに prompt の文字列を指定します。$ARGUMENTS のプレースホルダーで、フックの JSON 入力データをプロンプトのテキストに差し込みます。Claude が終える前に、すべての作業が完了したかを LLM に評価させる Stop フックの例:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "Evaluate if Claude should stop: $ARGUMENTS. Check if all tasks are complete."
}
]
}
]
}
}
| フィールド | 必須 | 内容 |
|---|---|---|
type |
はい | "prompt" でなければならない |
prompt |
はい | LLM に送るプロンプトのテキスト。$ARGUMENTS がフックの入力 JSON のプレースホルダー。$ARGUMENTS が無いと、入力 JSON はプロンプトの末尾に付く |
model |
いいえ | 評価に使うモデル。既定は、Claude Code がバックグラウンドの機能に使うモデル |
timeout |
いいえ | タイムアウトの秒数。既定は 30 |
continueOnBlock |
いいえ | 当てはまるイベントで、true なら、ターンを終えず、ok: false の理由を Claude に返して続ける。既定は false。イベントごとの挙動は、応答のスキーマの節を参照 |
応答のスキーマ#
LLM は、次を含む JSON で応答する必要があります。
{
"ok": true | false,
"reason": "Explanation for the decision",
"impossible": true | false
}
| フィールド | 内容 |
|---|---|
ok |
許可するなら true。false のときは、下のイベントごとの挙動を参照 |
reason |
ok が false のとき必須 |
impossible |
任意。モデルが、条件が決して満たされないと判断したとき、ok: false と一緒に返す。Stop と SubagentStop では、Claude Code は理由を返さずにターンを終わらせる。エージェント型フックとほかのイベントでは無視される |
ok: false のときに何が起きるかは、イベントで決まります。
StopとSubagentStop:理由が Claude の次の指示として返され、ターンが続く。応答がimpossible: trueも設定していれば、Claude Code は停止を許し、ターンが終わるPreToolUse:ツール呼び出しは拒否される。既定ではターンが終わり、拒否の理由が警告行としてチャットに出る。continueOnBlock: trueにすると、代わりに理由をツールエラーとして Claude に返し、Claude が調整して続けられる(コマンドフックのpermissionDecision: "deny"と同じ)。v2.1.210 より前は、拒否の理由がツールエラーとして Claude に返り、ターンが続いたPostToolUse:既定ではターンが終わり、理由が警告行としてチャットに出る。continueOnBlock: trueにすると、代わりに理由を Claude に返してターンを続けるPostToolBatch・UserPromptSubmit・UserPromptExpansion:ターンが終わり、理由が警告行として出る。これらのイベントは、continueにかかわらず、decision: "block"でターンを終えるPostToolUseFailureとTaskCreated:continueOnBlockにかかわらず、理由がツールエラーとして Claude に返り、ターンが続くTaskCompleted:ターン中にタスクが完了とマークされて発火したときは、continueOnBlockにかかわらず、理由がツールエラーとして Claude に返り、ターンが続く。チームメイトが止まって発火したときは、TeammateIdleのように動き、既定でチームメイトを止めるTeammateIdle:既定ではチームメイトが止まり、理由が警告行として出る。continueOnBlock: trueにすると、代わりに理由をチームメイトに返して作業を続けさせるPermissionRequest:ok: falseは効果がない。承認を拒否するには、hookSpecificOutput.decision.behavior: "deny"を返すコマンドフックを使うPermissionDenied:拒否はすでに起きているので、ok: falseは効果がない。このイベントが読む出力はhookSpecificOutput.retryだけで、プロンプト型とエージェント型のフックは設定できない(このイベントで動くが、出力は捨てられる)。retryを返すにはコマンドフックを使う
どのイベントでも、より細かい制御が要るときは、決定の制御に書いたイベントごとのフィールドを持つコマンドフックを使います。
止まる前に複数の条件を確かめる#
次の Stop フックは、詳しいプロンプトで、Claude が止まるのを許す前に3つの条件を確かめます。SubagentStop のフックも、同じ形式で、サブエージェントが止まるべきかを評価します。条件がまだ満たされず、モデルが "ok": false を返すと、Claude は与えられた理由を次の指示として作業を続けます。
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "You are evaluating whether Claude should stop working. Context: $ARGUMENTS\n\nAnalyze the conversation and determine if:\n1. All user-requested tasks are complete\n2. Any errors need to be addressed\n3. Follow-up work is needed\n\nRespond with JSON: {\"ok\": true} to allow stopping, or {\"ok\": false, \"reason\": \"your explanation\"} to continue working.",
"timeout": 30
}
]
}
]
}
}
エージェント型フック#
注意
エージェント型フックは実験的です。動作と設定は将来のリリースで変わることがあります。本番のワークフローでは、コマンドフックを選んでください。
エージェント型フック(type: "agent")は、複数ターンでツールを使える点を除き、プロンプト型フックに似ています。LLM を1回呼ぶ代わりに、ファイルを読み、コードを検索し、コードベースを調べて条件を確かめるサブエージェントを起動します。PermissionRequest を除き、プロンプト型フックと同じイベントに対応します。
エージェント型フックが発火したときの流れ:
- Claude Code が、あなたのプロンプトとフックの JSON 入力を持つサブエージェントを起動する
- サブエージェントが、Read・Grep・Glob などのツールで調べられる
- 最大 50 ターンの後、サブエージェントが構造化された
{ "ok": true/false }の判定を返す okがtrueなら、Claude Code は操作を許す。okがfalseなら、Claude Code は、応答のスキーマにあるように、そのイベントでcontinueOnBlock: trueを付けたプロンプト型フックと同じようにブロックを扱う
検証が、フックの入力データの評価だけでなく、実際のファイルやテストの出力を調べる必要があるときに役立ちます。
エージェント型フックの設定#
type を "agent" にし、prompt の文字列を指定します($ARGUMENTS はフックの入力 JSON のプレースホルダー)。設定のフィールドはプロンプト型フックと同じですが、エージェント型フックは既定のタイムアウトがより長い 60 秒で、continueOnBlock フィールドはありません。応答のスキーマは、許可なら { "ok": true }、ブロックなら { "ok": false, "reason": "..." } です。ok: false のとき、Claude Code は、同じイベントで continueOnBlock: true を付けたプロンプト型フックと同じように扱います。エージェント型フックには continueOnBlock フィールドも、プロンプト型の impossible フィールドへの対応もありません。
Claude が終える前に、すべてのユニットテストが通ることを確かめる Stop フックの例:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "agent",
"prompt": "Verify that all unit tests pass. Run the test suite and check the results. $ARGUMENTS",
"timeout": 120
}
]
}
]
}
}
バックグラウンドでフックを動かす#
既定では、フックは完了するまで Claude の実行をブロックします。デプロイ・テストスイート・外部 API の呼び出しのような長く動くタスクでは、"async": true を設定すると、Claude が作業を続ける間、フックがバックグラウンドで動きます。非同期のフックは、Claude の動作をブロックも制御もできません。decision・permissionDecision・continue などの応答のフィールドは、制御するはずだった操作がすでに完了しているので、効果がありません。
非同期フックの設定#
コマンドフックの設定に "async": true を足すと、Claude をブロックせずバックグラウンドで動かせます。このフィールドは type: "command" のフックだけで使えます。次のフックは、Write ツールの呼び出しのたびにテストスクリプトを動かします。run-tests.sh が動いている間も、Claude はすぐ作業を続けます。スクリプトが終わると、その出力は次の会話のターンで届きます。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "/path/to/run-tests.sh",
"async": true
}
]
}
]
}
}
- 非同期のフックがバックグラウンドで動き出すと、Claude Code はその
timeoutを強制しません。asyncRewakeで動かすフックでは、timeoutを引き続き強制します - Claude Code が非同期のフックの結果を届けるのは、セッションが動いている間だけです。
-pフラグの非対話モードでは、終了処理の時点でまだ動いている非同期のフックを Claude Code が kill し、結果をcancelledとして確定します。フックの作業をclaude -pのセッションより長く生かす必要があるなら、そこから完全に切り離したプロセスを起動します
非同期フックの動き#
非同期のフックが発火すると、Claude Code はフックのプロセスを起動し、終わるのを待たずにすぐ続けます。フックは、同期のフックと同じ JSON 入力を stdin で受け取ります。
- バックグラウンドのプロセスが終了した後、Claude Code は、フックの JSON レスポンスの
additionalContextとsystemMessageのフィールドを、次の会話のターンで Claude に届けます。同期のフックのsystemMessageと違い、どちらのフィールドもあなたには見えません - Claude Code は、その JSON レスポンスを同期のフックと同じ出力スキーマで検証し、型が誤った値のフィールド(文字列でない
systemMessageなど)は届けず捨てます。捨てたフィールドを名指しする警告は、--debugで見えます。v2.1.202 より前は、非同期のフックの不正な JSON 出力でセッションがクラッシュし、そのクラッシュがセッションを再開するたびに繰り返されることがありました - 非同期のフックの完了通知は、既定では出ません。見るには、Ctrl+O で verbose モードをオンにするか、
--verboseで Claude Code を起動します
例:ファイルの変更後にテストを動かす#
Claude がファイルを書くたびに、バックグラウンドでテストスイートを動かし、テストが終わったら結果を Claude に報告するフックです。次のスクリプトをプロジェクトの .claude/hooks/run-tests-async.sh に保存し、chmod +x で実行権限を付けます。
#!/bin/bash
# run-tests-async.sh
# Read hook input from stdin
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
# Only run tests for source files
if [[ "$FILE_PATH" != *.ts && "$FILE_PATH" != *.js ]]; then
exit 0
fi
# Run tests and report results to Claude via additionalContext
RESULT=$(npm test 2>&1)
EXIT_CODE=$?
if [ $EXIT_CODE -eq 0 ]; then
MSG="Tests passed after editing $FILE_PATH"
else
MSG="Tests failed after editing $FILE_PATH: $RESULT"
fi
jq -nc --arg msg "$MSG" '{hookSpecificOutput: {hookEventName: "PostToolUse", additionalContext: $msg}}'
そのうえで、プロジェクトルートの .claude/settings.json に次の設定を足します。async: true のフラグで、テストが動く間も Claude が作業を続けられます。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/run-tests-async.sh",
"args": [],
"async": true
}
]
}
]
}
}
非同期フックの制約#
同期のフックに比べて、非同期のフックにはさらに制約があります。
- フックの出力は次の会話のターンで届く。セッションがアイドルなら、次のユーザーの操作まで応答が待つ。例外:終了コード 2 で終わる
asyncRewakeのフックは、セッションがアイドルでも Claude をすぐ起こす - 実行のたびに別のバックグラウンドのプロセスが作られる。同じ非同期フックが何度発火しても、重複は取り除かれない
セキュリティ#
注意
コマンドフックは、あなたのユーザーの全権限でシェルコマンドを実行します。ユーザーアカウントがアクセスできるどのファイルも変更・削除・参照できます。設定に加える前に、すべてのフックのコマンドを確認してテストしてください。
ワークスペースの信頼#
Claude Code は、設定ファイルのフックを動かす前に、ワークスペースの信頼を確かめます。何が信頼済みとして数えられるかは、セッションの種類で決まります。
- 対話セッション:Claude Code は、そのフォルダ(またはその信頼が及ぶ親ディレクトリ)のワークスペースの信頼ダイアログを承認するまで、あなた自身の
~/.claude/settings.jsonを含むすべての設定ファイルのフックを動かさない -pまたは SDK セッション:Claude Code はダイアログを出さず、フォルダを信頼済みとして扱うので、リポジトリの.claude/settings.jsonにコミットされたフックは、一度も信頼していないフォルダで動く
自分が書いていないリポジトリに対して claude -p をスクリプトで動かす前に、その .claude/ の設定ファイルを確認するか、--bare で始めるか、--settings '{"disableAllHooks": true}' でその実行のフックをオフにします。プロジェクトのサブエージェントの frontmatter のフックは、設定ファイルのフックより厳しい規則に従います。
セキュリティのベストプラクティス#
フックを書くときは、次を心に留めます。
- 入力を検証し無害化する:入力データを決して盲目的に信頼しない
- シェルの変数は必ず引用符で囲む:
$VARではなく"$VAR" - パストラバーサルを防ぐ:ファイルパスの
..を確かめる - 絶対パスを使う:スクリプトにフルパスを指定する。exec 形式では
${CLAUDE_PROJECT_DIR}を使えば、パスに引用符は要らない。シェル形式では二重引用符で囲む - 機密のファイルを避ける:
.env・.git/・鍵などは対象にしない
Windows の PowerShell で動かす#
Windows では、コマンドフックに "shell": "powershell" を設定して、個々のフックを PowerShell で動かせます。Claude Code は、PowerShell 7 以降の実行ファイル pwsh.exe を自動で検出し、Windows PowerShell 5.1 の powershell.exe にフォールバックします。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"shell": "powershell",
"command": "Write-Host 'File written'"
}
]
}
]
}
}
- PowerShell のシェル形式のコマンドからプロジェクトルートを参照するには、
${CLAUDE_PROJECT_DIR}か$env:CLAUDE_PROJECT_DIRと書きます。Claude Code は、PowerShell のシェル形式のコマンドの${CLAUDE_PROJECT_DIR}・${CLAUDE_PLUGIN_ROOT}・${CLAUDE_PLUGIN_DATA}のプレースホルダーを、settings.json・プラグイン・スキルのどこで定義されたフックでも、PowerShell の${env:NAME}の形に書き換えます。PowerShell は、解析の後、書き出された環境から値を解決するので、プレースホルダーは二重引用符の文字列の中では働き、PowerShell が変数を展開しない単一引用符の文字列の中では働きません - PowerShell のフックでは、素の
$CLAUDE_PROJECT_DIRの書き方を使いません。PowerShell はそれを未定義のローカル変数として解析して$nullに解決し、スクリプトのパスからプロジェクトルートの接頭辞が落ちます。Claude Code はその形を書き換えず、代わりにデバッグログに警告を出します
$env: の形で、プロジェクトのスクリプトを動かす settings.json のフックの例:
{
"type": "command",
"shell": "powershell",
"command": "& \"$env:CLAUDE_PROJECT_DIR\\.claude\\hooks\\check.ps1\""
}
フックをデバッグする#
フックの実行の詳細は、デバッグログのファイルに書かれます。ログを決まった場所に書くには claude --debug-file <path> で起動するか、claude --debug を実行して ~/.claude/debug/<session-id>.txt のログを読みます。--debug フラグは、ターミナルには出力しません。
- たとえば、
hook-ranを出力するコマンドを持つWriteへのPostToolUseのフックは、次のような項目を作ります
2026-07-19T02:03:24.382Z [DEBUG] Hook output does not start with {, treating as plain text
2026-07-19T02:03:24.382Z [DEBUG] "Hook PostToolUse:Write (PostToolUse) success:\nhook-ran"
公式ドキュメント(英語)
2026年10月5日時点の内容をもとに、日本語でまとめています。