一行要約

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 / --maintenanceCI やスクリプトでの一度きりの準備用
UserPromptSubmitプロンプト送信時、Claude が処理する前
UserPromptExpansionユーザーが打ったコマンドがプロンプトに展開されるとき。展開をブロックできる
PreToolUseツール呼び出しの実行前。ブロックできる
PermissionRequestツール呼び出しに権限判断が必要になったとき
PermissionDeniedauto mode の分類器が拒否したとき。{retry: true} を返すとモデルに再試行を許可できる
PostToolUseツール呼び出しの成功後
PostToolUseFailureツール呼び出しの失敗後
PostToolBatch並列ツール呼び出しのバッチ全体が解決した後、次のモデル呼び出しの前
Notification通知が送られるとき
MessageDisplayassistant のメッセージテキストが表示される間
SubagentStart / SubagentStopsubagent の起動 / 終了
TaskCreated / TaskCompletedタスクの作成 / 完了マーク時
StopClaude が応答を終えたとき
StopFailureAPI エラーでターンが終わったとき。出力と exit code は無視される
TeammateIdleagent team の teammate が idle になろうとするとき
InstructionsLoadedCLAUDE.md や .claude/rules/*.md が context にロードされたとき。どのファイルがいつロードされたかのデバッグに有用
ConfigChangeセッション中に設定ファイルが変わったとき
CwdChanged作業ディレクトリが変わったとき(Claude が cd を実行したときなど)。direnv のような反応的な環境管理に有用
DirectoryAdded/add-dir で作業ディレクトリが追加されたとき
FileChanged監視対象ファイルがディスク上で変更されたとき。matcher で監視対象のファイル名を指定する
WorktreeCreate / WorktreeRemoveworktree の作成 / 削除時。既定の git 挙動を置き換える
PreCompact / PostCompactcontext compaction の前 / 後
Elicitation / ElicitationResultMCP サーバがツール呼び出し中にユーザー入力を要求したとき / 応答後
SessionEndセッション終了時

hook の5つの型

type内容
command(既定)シェルコマンドを実行する
httpURL にイベントデータを POST する
mcp_tool接続済みの MCP サーバのツールを呼ぶ
prompt単発の LLM 評価(既定は Haiku、model で変更可)
agentツールを使える多ターンの検証(experimental)

入出力と exit code

hook は stdin / stdout / stderr と exit code で Claude Code と通信する。イベント固有のデータが JSON で stdin に渡る(共通フィールドは session_idcwd)。

exit code挙動
0異議なし。PreToolUse では承認ではない — 通常の権限フローがそのまま適用される。UserPromptSubmit / UserPromptExpansion / SessionStart では stdout が Claude の context に追加される
2ブロックする。 stderr に理由を書くと Claude にフィードバックとして届く。ブロックできないイベントもあるSessionStartSetupNotification など。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 を無視する。

PreToolUsepermissionDecision の値:

  • "allow" — 対話的な権限プロンプトをスキップする。ただし deny / ask ルール(企業の managed deny list を含む)は依然として適用される
  • "deny" — ツール呼び出しをキャンセルし、理由を Claude に送る
  • "ask" — 通常どおりユーザーに確認する
  • "defer" — 非対話モード(-p)でのみ。ツール呼び出しを保持したままプロセスを終了し、Agent SDK のラッパーが入力を集めて再開できるようにする

UserPromptSubmit では hookSpecificOutput.additionalContext で Claude の context にテキストを注入する。

Nest additionalContext inside hookSpecificOutput; if you place it at the top level of the JSON, Claude Code silently ignores it. additionalContexthookSpecificOutput の内側に入れよ。JSON の最上位に置くと、Claude Code はそれを黙って無視する。

複数の hook が同じイベントにマッチしたとき

  • すべての hook が並列に走り、全部が完了してから結果がマージされる
  • 1つが deny を返しても、兄弟の hook の実行は止まらない

Don’t rely on one hook’s deny to suppress side effects in another hook. ある hook の deny が、別の hook の副作用を抑えてくれることを当てにするな。

  • PreToolUse の権限判断は最も制限的なものが勝つ。順序は deny > defer > ask > allow
  • additionalContext全 hook の分が保持されてまとめて Claude に渡る

matcher と if

  • matcher — グループ単位でツール名だけで絞る。パイプ | で複数指定できる(Edit|Write
  • if — permission rule 構文でツール名と引数を合わせて絞る。マッチしたときだけ hook プロセスが起動する

if の挙動(複合コマンドの扱いが重要):

if パターンBash コマンド走るか理由
Bash(git *)git pushyesコマンド名が一致
Bash(git *)npm test && git pushyes各サブコマンドが検査される
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 はツールイベントでのみ機能するPreToolUsePostToolUsePostToolUseFailurePermissionRequestPermissionDenied)。他のイベントに付けると hook が走らなくなる。

置き場所とスコープ

場所スコープ共有
~/.claude/settings.json全プロジェクト不可(マシンローカル)
.claude/settings.json単一プロジェクト(リポジトリにコミットできる)
.claude/settings.local.json単一プロジェクト不可(gitignore される)
managed policy settings組織全体可(管理者が制御)
plugin の hooks/hooks.jsonplugin が有効な間
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 / SubagentStopreason が Claude に戻され、作業を続けさせる
    • PreToolUse — ツール呼び出しが拒否される。既定ではターンが終わり、警告行として表示される。continueOnBlock: true を設定すると reason がツールエラーとして Claude に返り、続行できる
    • PostToolUse — 既定でターンが終わる。continueOnBlock: true で続行
使うとき
prompt hookhook の入力データだけで判断できるとき
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 0

exit 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 hookallowedEnvVars に載せた変数だけが展開される):

{
  "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-teamsTeammateIdle / TaskCreated / TaskCompleted

未取得の派生リンク