一行要約
hook の価値は「確実に起きる」ことにある — LLM が実行を選ぶかどうかに依存しない決定的な制御であり、判断が要る場面のために prompt / agent 型(LLM を挟む hook)まで用意されている。
要点
Hooks are user-defined shell commands. Claude Code runs them at specific points in its lifecycle, which gives you deterministic control: certain actions always happen rather than relying on the LLM to choose to run them. hook はユーザーが定義するシェルコマンドである。Claude Code はそれを自身のライフサイクルの特定の時点で実行する。これにより決定的な制御が得られる。LLM が実行を選ぶことに頼るのではなく、特定の動作が必ず起きる。
lifecycle イベント一覧
| イベント | 発火タイミング |
|---|---|
SessionStart | セッション開始 / 再開時 |
Setup | --init-only、または -p モードの --init / --maintenance。CI やスクリプトでの一度きりの準備用 |
UserPromptSubmit | プロンプト送信時、Claude が処理する前 |
UserPromptExpansion | ユーザーが打ったコマンドがプロンプトに展開されるとき。展開をブロックできる |
PreToolUse | ツール呼び出しの実行前。ブロックできる |
PermissionRequest | ツール呼び出しに権限判断が必要になったとき |
PermissionDenied | auto mode の分類器が拒否したとき。{retry: true} を返すとモデルに再試行を許可できる |
PostToolUse | ツール呼び出しの成功後 |
PostToolUseFailure | ツール呼び出しの失敗後 |
PostToolBatch | 並列ツール呼び出しのバッチ全体が解決した後、次のモデル呼び出しの前 |
Notification | 通知が送られるとき |
MessageDisplay | assistant のメッセージテキストが表示される間 |
SubagentStart / SubagentStop | subagent の起動 / 終了 |
TaskCreated / TaskCompleted | タスクの作成 / 完了マーク時 |
Stop | Claude が応答を終えたとき |
StopFailure | API エラーでターンが終わったとき。出力と exit code は無視される |
TeammateIdle | agent team の teammate が idle になろうとするとき |
InstructionsLoaded | CLAUDE.md や .claude/rules/*.md が context にロードされたとき。どのファイルがいつロードされたかのデバッグに有用 |
ConfigChange | セッション中に設定ファイルが変わったとき |
CwdChanged | 作業ディレクトリが変わったとき(Claude が cd を実行したときなど)。direnv のような反応的な環境管理に有用 |
DirectoryAdded | /add-dir で作業ディレクトリが追加されたとき |
FileChanged | 監視対象ファイルがディスク上で変更されたとき。matcher で監視対象のファイル名を指定する |
WorktreeCreate / WorktreeRemove | worktree の作成 / 削除時。既定の git 挙動を置き換える |
PreCompact / PostCompact | context compaction の前 / 後 |
Elicitation / ElicitationResult | MCP サーバがツール呼び出し中にユーザー入力を要求したとき / 応答後 |
SessionEnd | セッション終了時 |
hook の5つの型
type | 内容 |
|---|---|
command(既定) | シェルコマンドを実行する |
http | URL にイベントデータを POST する |
mcp_tool | 接続済みの MCP サーバのツールを呼ぶ |
prompt | 単発の LLM 評価(既定は Haiku、model で変更可) |
agent | ツールを使える多ターンの検証(experimental) |
入出力と exit code
hook は stdin / stdout / stderr と exit code で Claude Code と通信する。イベント固有のデータが JSON で stdin に渡る(共通フィールドは session_id と cwd)。
| exit code | 挙動 |
|---|---|
| 0 | 異議なし。PreToolUse では承認ではない — 通常の権限フローがそのまま適用される。UserPromptSubmit / UserPromptExpansion / SessionStart では stdout が Claude の context に追加される |
| 2 | ブロックする。 stderr に理由を書くと Claude にフィードバックとして届く。ブロックできないイベントもある(SessionStart、Setup、Notification など。stderr はユーザーに表示され実行は続く) |
| その他 | 実行は続く。transcript に <hook name> hook error と stderr の1行目が出る。全文は claude --debug か /debug で見る |
構造化 JSON 出力(exit 0 で stdout に JSON を出す)を使うともっと制御できる。
Use exit 2 to block with a stderr message, or exit 0 with JSON for structured control. Don’t mix them: Claude Code ignores JSON when you exit 2. ブロックするには、終了コード 2 と stderr のメッセージを使うか、あるいは終了コード 0 と JSON による構造化された制御を使え。両者を混ぜるな。終了コード 2 のとき、Claude Code は JSON を無視する。
PreToolUse の permissionDecision の値:
"allow"— 対話的な権限プロンプトをスキップする。ただし deny / ask ルール(企業の managed deny list を含む)は依然として適用される"deny"— ツール呼び出しをキャンセルし、理由を Claude に送る"ask"— 通常どおりユーザーに確認する"defer"— 非対話モード(-p)でのみ。ツール呼び出しを保持したままプロセスを終了し、Agent SDK のラッパーが入力を集めて再開できるようにする
UserPromptSubmit では hookSpecificOutput.additionalContext で Claude の context にテキストを注入する。
Nest
additionalContextinsidehookSpecificOutput; if you place it at the top level of the JSON, Claude Code silently ignores it.additionalContextはhookSpecificOutputの内側に入れよ。JSON の最上位に置くと、Claude Code はそれを黙って無視する。
複数の hook が同じイベントにマッチしたとき
- すべての hook が並列に走り、全部が完了してから結果がマージされる
- 1つが
denyを返しても、兄弟の hook の実行は止まらない
Don’t rely on one hook’s
denyto suppress side effects in another hook. ある hook のdenyが、別の hook の副作用を抑えてくれることを当てにするな。
PreToolUseの権限判断は最も制限的なものが勝つ。順序はdeny>defer>ask>allowadditionalContextは全 hook の分が保持されてまとめて Claude に渡る
matcher と if
matcher— グループ単位でツール名だけで絞る。パイプ|で複数指定できる(Edit|Write)if— permission rule 構文でツール名と引数を合わせて絞る。マッチしたときだけ hook プロセスが起動する
if の挙動(複合コマンドの扱いが重要):
if パターン | Bash コマンド | 走るか | 理由 |
|---|---|---|---|
Bash(git *) | git push | yes | コマンド名が一致 |
Bash(git *) | npm test && git push | yes | 各サブコマンドが検査される |
Bash(git *) | echo $(git log) | yes | $() やバッククォート内も検査される |
Bash(git *) | echo $(date) | no | どのサブコマンドも一致しない |
Bash(git push *) | echo $(date) | yes | コマンド名以上を指定したパターンは、$() / バッククォート / $VAR があると念のため走る |
The filter also fails open, running your hook regardless of pattern, when the Bash command can’t be parsed. Because the filter is best-effort, use the permission system rather than a hook to enforce a hard allow or deny. Bash のコマンドを解析できないとき、フィルタはフェイルオープンし、パターンによらず hook を実行する。フィルタは best-effort なので、厳格な許可・拒否を強制したいなら、hook ではなく permission の仕組みを使え。
if はツールイベントでのみ機能する(PreToolUse、PostToolUse、PostToolUseFailure、PermissionRequest、PermissionDenied)。他のイベントに付けると hook が走らなくなる。
置き場所とスコープ
| 場所 | スコープ | 共有 |
|---|---|---|
~/.claude/settings.json | 全プロジェクト | 不可(マシンローカル) |
.claude/settings.json | 単一プロジェクト | 可(リポジトリにコミットできる) |
.claude/settings.local.json | 単一プロジェクト | 不可(gitignore される) |
| managed policy settings | 組織全体 | 可(管理者が制御) |
plugin の hooks/hooks.json | plugin が有効な間 | 可 |
| skill / agent の frontmatter | その skill / agent が有効な間 | 可 |
/hooksでイベント別に一覧できる。このメニューは読み取り専用 — 追加・変更・削除は設定 JSON を直接編集する"disableAllHooks": trueで無効化できる。ただし managed settings の hook は、そちらにも同じ設定を入れない限り走り続ける- Claude Code の実行中に設定ファイルを直接編集しても、file watcher が変更を拾う
prompt hook と agent hook の使い分け
どちらも {"ok": bool, "reason": string} を返す。
"ok": true— 続行"ok": false— イベントによって挙動が変わるStop/SubagentStop—reasonが Claude に戻され、作業を続けさせるPreToolUse— ツール呼び出しが拒否される。既定ではターンが終わり、警告行として表示される。continueOnBlock: trueを設定するとreasonがツールエラーとして Claude に返り、続行できるPostToolUse— 既定でターンが終わる。continueOnBlock: trueで続行
| 使うとき | |
|---|---|
| prompt hook | hook の入力データだけで判断できるとき |
| agent hook | コードベースの実際の状態に照らして検証する必要があるとき。ファイルを読み、コマンドを走らせられる。既定タイムアウト 60秒、最大50ツールターン |
agent hook では $ARGUMENTS が hook の JSON 入力に置換される。
そのまま使える具体例
編集のたびに Prettier を走らせる(.claude/settings.json):
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
}
]
}
]
}
}保護ファイルの編集をブロックする(.claude/hooks/protect-files.sh):
#!/bin/bash
# protect-files.sh
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
# Normalize Windows backslash separators so the patterns below match
FILE_PATH="${FILE_PATH//\\//}"
PROTECTED_PATTERNS=(".env" "package-lock.json" ".git/")
for pattern in "${PROTECTED_PATTERNS[@]}"; do
if [[ "$FILE_PATH" == *"$pattern"* ]]; then
echo "Blocked: $FILE_PATH matches protected pattern '$pattern'" >&2
exit 2
fi
done
exit 0exit 2 でブロックする最小形:
#!/bin/bash
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command')
if echo "$COMMAND" | grep -q "drop table"; then
echo "Blocked: dropping tables is not allowed" >&2 # stderr becomes Claude's feedback
exit 2 # exit 2 = block the action
fi
exit 0 # exit 0 = no decision; the normal permission flow applies構造化 JSON で拒否して理由を伝える:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Use rg instead of grep for better performance"
}
}毎プロンプトに context を注入する:
{
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"additionalContext": "Current branch: release-42. Deploy freeze until Friday."
}
}ログ用と guardrail 用の2つを同じイベントに登録する(両方走り、deny が勝ち、ログは残る):
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "jq -r .tool_input.command >> ~/.claude/bash.log"
},
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-rm-rf.sh"
}
]
}
]
}
}if で git コマンドのときだけ走らせる:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(git *)",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-git-policy.sh"
}
]
}
]
}
}prompt hook で「本当に終わったか」を判定させる:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "Check if all tasks are complete. If not, respond with {\"ok\": false, \"reason\": \"what remains to be done\"}."
}
]
}
]
}
}agent hook でテストの通過を検証させる:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "agent",
"prompt": "Verify that all unit tests pass. Run the test suite and check the results. $ARGUMENTS",
"timeout": 120
}
]
}
]
}
}macOS のデスクトップ通知(~/.claude/settings.json):
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'"
}
]
}
]
}
}HTTP hook(allowedEnvVars に載せた変数だけが展開される):
{
"hooks": {
"PostToolUse": [
{
"hooks": [
{
"type": "http",
"url": "http://localhost:8080/hooks/tool-use",
"headers": {
"Authorization": "Bearer $MY_TOKEN"
},
"allowedEnvVars": ["MY_TOKEN"]
}
]
}
]
}
}原典で言及されている関連文書
- features-overview — 「強制したいことは hook にする」の判断ガイド
- memory — CLAUDE.md は指示であって強制ではない(だから hook が要る)
- permission-modes — hook と permission の関係
- skills — skill frontmatter の
hooks - agent-teams —
TeammateIdle/TaskCreated/TaskCompleted
未取得の派生リンク
- https://code.claude.com/docs/en/hooks — 完全なイベントスキーマ、非同期 hook、MCP tool hook
- https://code.claude.com/docs/en/security-guidance — 別モデルのレビューを走らせる本番例