メインコンテンツまでスキップ

フック:決定論的な自動化

上級

フックは、ライフサイクルの定められたポイントで Claude Code が自動的に実行するシェルコマンドです。権限がアクションを許可するかどうかを決めるのに対し、フックではそのアクションの前後であなたが決定論的なロジック(フォーマット、検証、ログ記録、ゲート、マスキング)を実行できます。「忘れずにやってね」というお願いではなく、動作を保証にするための仕組みです。

What you'll learn
  • 指示・権限・スキルではなく、フックを選ぶべきとき
  • ライフサイクル全体:セッション、プロンプト、ツール、サブエージェント、タスク、コンパクション、ターン終了のイベント
  • フックの配線方法:イベント、マッチャー、そして stdin の JSON ペイロード
  • フックがアクションをブロックする 2 つの方法 — 終了コード 2 と stdout の JSON
  • イベント間で共通の JSON 出力フィールド:continue、additionalContext、systemMessage、reloadSkills、sessionTitle、suppressOutput、updatedInput
  • 高速で安全なフックと、遅くて沈黙するフックを分けるグッドプラクティスとよくある間違い

フックを使うべきとき​

動作を単に頼むのではなく保証したいときにフックを使います。よくある用途は、それぞれライフサイクルイベントに対応します。

  • ファイル編集のたびに自動フォーマット/lint(Edit|Write に対する PostToolUse)。
  • ルールに違反するアクションを実行前にブロック(PreToolUse)。
  • アシスタントのメッセージがターミナルに出る前にマスキング(MessageDisplay)。
  • SessionStart の後にスキルを再読み込み、またはセッションタイトルを設定。
  • セッション終了、タスク完了、ターン失敗時に通知またはログ記録(Stop、StopFailure、SessionEnd)。
  • セッション開始時や指示の読み込み時にコンテキストを注入。

仕組み​

フックは settings.json に登録し、イベント(そして多くの場合はマッチャー — 通常はツール名、ときにはサブエージェントの種類、コンパクションのトリガー、通知の種類)に対応づけます。イベントが発火すると、Claude はあなたのコマンドを実行し、stdin で JSON ペイロード(イベント名、セッション、ツール名や入力などイベント固有のフィールド)を渡します。コマンドの終了コードと stdout が次の動作を決めます。

Guided walkthrough1 of 4
  1. 対象にしたいライフサイクルイベントの下に、settings.json でフックを登録します — たとえばツール実行後の自動化なら PostToolUse、マスキングなら MessageDisplay。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write" }
]
}
]
}
}

上のフックは、stdin の JSON(.tool_input.file_path)から編集されたファイルのパスを取り出してフォーマットします。環境変数にパスが入っていると思い込まないでください — stdin から読みます。 スクリプトの場所を指すには ${CLAUDE_PROJECT_DIR} のようなパスのプレースホルダーが使えます。

ライフサイクル早見表​

Claude Code のフックは、ツール呼び出しだけでなくセッション全体にわたります。ロジックをどこに掛けるかが分かるよう、フェーズごとにまとめました。

フェーズイベント典型的な用途
セッションSessionStart、SessionEnd、Setup、InstructionsLoaded、ConfigChange、CwdChanged、DirectoryAdded、FileChanged開始時にコンテキストを注入、終了時にスナップショット、CLAUDE.md の再読み込みやディスク上の .env の変更に反応
プロンプトUserPromptSubmit、UserPromptExpansionClaude がプロンプトを見る前にゲートまたは書き換え
ツールPreToolUse、PermissionRequest、PermissionDenied、PostToolUse、PostToolUseFailure、PostToolBatchアクションの許可/拒否、人間へのエスカレーション、編集後のフォーマット、auto モードで拒否された後の再試行
サブエージェントSubagentStart、SubagentStop起動/停止のログ記録、サブエージェントが結果を返すのをブロック
タスクTaskCreated、TaskCompleted誤って作られたタスクのロールバック、完了のゲート
コンパクションPreCompact、PostCompactコンパクション前にトランスクリプトをスナップショット、後で検証
モデルPreModelSwitch、PostModelSwitch適用前にモデル切り替えをブロック、切り替え後(再開したセッションがモデルを復元した場合を含む)に反応
ターンStop、StopFailure、Notification、MessageDisplay完了時の通知、API 障害時のアラート、表示テキストのマスキング
チーム + MCPTeammateIdle、Elicitation、ElicitationResult、WorktreeCreate、WorktreeRemoveセッション間の連携、MCP の elicitation のゲート、worktree 操作のラップ

各イベントの stdin フィールド、マッチャーの文法、ブロックのルールの正確な内容は公式のフックリファレンスにあります。上のカタログは地図で、リファレンスが現地です。

フックがブロックする方法​

イベントに応じて 2 通りあります。

  • 終了コード 2 — フックがアクションを失敗させ、stderr に書いた内容が Claude の見るメッセージになります。シンプルで、ブロック可能なイベント(PreToolUse、UserPromptSubmit、UserPromptExpansion、Stop、SubagentStop、TeammateIdle、TaskCreated、TaskCompleted、ConfigChange、PostToolBatch、PreCompact、PreModelSwitch、Elicitation、ElicitationResult、WorktreeCreate、WorktreeRemove)のコマンドフックで使えます。PermissionRequest では終了コード 2 は無視されます。代わりに decision オブジェクトで拒否してください。
  • stdout の JSON(終了コード 0) — 構造化された決定を返します。PreToolUse では、permissionDecision に "allow"、"deny"、"ask"(その呼び出しをユーザーに提示)、"defer"(アプリが再開する -p 実行でのみ使用)のいずれかを指定し、permissionDecisionReason を付けます。ブロックしないイベント(PostToolUse、Notification、MessageDisplay、StopFailure、ほとんどのセッションイベント)はブロックの試みを無視します — ただし JSON 出力で Claude が次に見る内容を誘導することはできます。

以下のスクリプトは Bash ツールに対する PreToolUse フックです。上から順に読んでください:stdin からコマンドを取り出し、破壊的に見えたら stderr に理由を書いて終了コード 2 でブロックします。

#!/usr/bin/env bash
# PreToolUse hook on the Bash tool: refuse to delete things.
command=$(jq -r '.tool_input.command' < /dev/stdin)
if [[ "$command" == rm\ * || "$command" == *"rm -rf"* ]]; then
echo "Blocked: destructive 'rm' is not allowed by policy." >&2
exit 2
fi
exit 0

共通の JSON 出力フィールド​

ほとんどのイベントは、次に起きることを誘導する構造化 JSON オブジェクトを stdout で受け付けます。これは permissionDecision のようなイベント固有の決定フィールドとは別物です。特に便利なもの:

フィールド動作使える場所
continuefalse の場合、Claude は処理を完全に停止します — イベント固有のあらゆる決定より優先されますほとんどのイベント
systemMessageClaude のコンテキスト/トランスクリプトにメッセージを追加し、なぜ介入したかが分かるようにしますほとんどのブロック可能なイベント
additionalContext次のターンで Claude が読んで活用できる追加コンテキストを注入しますSessionStart、UserPromptSubmit、ほか
reloadSkillsスキルのディレクトリを再スキャンし、書いたばかりのスキルをセッションが認識できるようにしますSessionStart(およびその他のセッションイベント)
sessionTitleUI のセッションタイトルを設定します — SessionStart では hookSpecificOutput の下に置きますSessionStart
suppressOutputツールの出力を Claude から隠します(出力の多いコマンドに便利)ツールイベント
updatedInput実行前にツールの入力を書き換えます(例:--dry-run フラグを追加)PreToolUse
terminalSequenceターミナルのエスケープシーケンス(ベル、ウィンドウタイトル、通知バッジ)を出力しますNotification、StopFailure、ほか

イベント固有の決定フィールド — PreToolUse の permissionDecision("allow"、"deny"、"ask"、"defer" と permissionDecisionReason)、PermissionRequest の decision.behavior("allow" または "deny")、PermissionDenied で Claude に拒否された呼び出しを再試行させる retry: true — は、これらの共通フィールドの上に重ねて使います。

メンタルモデル​

PreToolUse フックはアクションの前に実行されてブロックでき、PostToolUse フックはアクションが成功した後に実行されて結果に反応します。

フックイベント早見
Enter キーまたはスペースキーでカードを裏返します。左右の矢印キーでカードを移動できます。用語を表示しました。
1 / 6

グッドプラクティス​

  • フックは高速かつ冪等に保つ — 頻繁に実行されます。SessionEnd フックは、すべてのフックで 1.5 秒の予算を 1 つ共有するため、セッション終了時の処理は最小限にしてください。
  • 本当の問題では大きな音で失敗する。ただし見た目の問題ではブロックしない。
  • フックの出力を Claude へのフィードバックとして扱う — stderr(終了コード 2)または systemMessage(JSON)の明確なメッセージは、Claude の自己修正に役立ちます。
  • 「ブロック」以上のことを伝えたいなら、終了コードより JSON 出力を選ぶ — PreToolUse の permissionDecision: "ask" は、扉を叩きつけずに人間へ判断を委ねます。additionalContext なら、拒否するだけでなく誘導できます。
  • フックはあなたのシェルの権限で動きます — 自分で書いていないフックは必ず確認してください(サードパーティコードのレビュー)。

よくある間違い​

  • ファイルパスを環境変数から読む。 パスは stdin の JSON(.tool_input.file_path)にあり、$CLAUDE_FILE_PATH にはありません。stdin を jq にパイプしてください。
  • 無言のブロック。 PreToolUse フックが stderr に何も書かずに終了コード 2 で終わると、Claude はブロックされても理由が分からず、対応できません。必ず明確な理由を書いてください(または permissionDecisionReason を使う)。
  • ブロックしないイベントをブロックしようとする。 PostToolUse はツールが実行された後に発火します — 終了コード 2 は Claude の耳元にメッセージを届けるだけで、アクションを取り消せません。ゲートが必要なら PreToolUse に掛けてください。
  • 遅いフック。 PostToolUse フックは、一致する編集のたびに実行されます。3 秒かかる linter はセッション全体を重くします — フックは高速に保ち、できれば変更された部分だけを対象にしましょう。
  • 広すぎるマッチャー。 matcher: ".*" はすべてのツールで発火します。正確な名前、Edit|Write のような一覧、またはハンドラーごとの if フィールド(例:"if": "Bash(git push *)")で絞り込んでください。
  • ツール以外のイベントにもマッチャーがあることを忘れる。 SessionStart は startup|resume|clear|compact|fork に、Notification は permission_prompt|idle_prompt|agent_completed のような種類に、PreCompact は manual|auto にマッチします。間違った部分集合に配線したフックは、発火しすぎるか、まったく発火しません。
  • 自分で書いていないフックを信用する。 フックは任意のシェルをあなたの権限で実行します。プラグインやテンプレート由来のフックは、使う前に必ず確認してください — サードパーティコードのレビューを参照。
Watch out
  • フックは任意のシェルをあなたの権限で実行します — プラグインやテンプレート由来のフックは、読まずに配線しないでください。

盗む価値のあるレシピ​

コピー&ペーストで使えるスターターは フックと settings.json のレシピにあります。新しめのイベントの形が分かる、効果の高いものをいくつか紹介します。

編集したファイルを自動フォーマット(Edit|Write に対する PostToolUse)

{
"hooks": {
  "PostToolUse": [
    {
      "matcher": "Edit|Write",
      "hooks": [
        { "type": "command", "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write" }
      ]
    }
  ]
}
}

開始時にプロジェクトごとのセッションタイトルを設定(SessionStart)

{
"hooks": {
  "SessionStart": [
    {
      "matcher": "startup|resume",
      "hooks": [
        {
          "type": "command",
          "command": "jq -nc --arg t \"$(basename \"$PWD\") — $(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo detached)\" '{hookSpecificOutput:{sessionTitle:$t}}'"
        }
      ]
    }
  ]
}
}

SessionStart の後にスキルを再読み込み(書いたばかりのスキルを認識)

{
"hooks": {
  "SessionStart": [
    {
      "matcher": "startup|resume",
      "hooks": [
        { "type": "command", "command": "printf '{\"reloadSkills\": true}\n'" }
      ]
    }
  ]
}
}

表示されるアシスタントのテキストから API キーをマスキング(MessageDisplay)

{
"hooks": {
  "MessageDisplay": [
    {
      "hooks": [
        {
          "type": "command",
          "command": "jq -r '.message.content' | sed -E 's/(sk-[A-Za-z0-9_-]{20,})/sk-***REDACTED***/g'"
        }
      ]
    }
  ]
}
}

ターンの失敗を通知(StopFailure — レート制限、過負荷、認証)

{
"hooks": {
  "StopFailure": [
    {
      "matcher": "rate_limit|overloaded|authentication_failed|billing_error",
      "hooks": [
        { "type": "command", "command": "osascript -e 'display notification \"Claude Code turn failed — check the terminal\" with title \"Claude Code\"'" }
      ]
    }
  ]
}
}

理解度チェック

0/4
  1. フックは、編集されたばかりのファイルのパスをどこで見つけますか?
  2. PreToolUse フックが終了コード 2 で終了すると、何が起きますか?
  3. 再起動せずに、.claude/skills/ に追加したばかりのスキルをセッションに認識させたいとき、どのイベントでどの出力フィールドを使いますか?
  4. Claude のテキストがユーザーの画面に表示される前に、秘密情報をマスキングしたい。どのイベントを使いますか?
Key takeaways
  • フックは動作を「お願い」ではなく「保証」にします — 権限が許可か拒否しかできないアクションの周囲に、決定論的なロジックを置けます。
  • ライフサイクルはセッション、プロンプト、ツール、サブエージェント、タスク、コンパクション、ターンの各フェーズにまたがる 30 以上のイベントに及びます — スクリプトを書く前に適切なフェーズを選びましょう。
  • settings.json にイベントとマッチャーを登録します。Claude は stdin に JSON ペイロードをパイプし、あなたの終了コードと出力を読み取ります。
  • ファイルパスは stdin(.tool_input.file_path)から読みます — 環境変数からではありません。
  • 終了コード 2(stderr がメッセージになる)または stdout の構造化 JSON(ツールイベントでは permissionDecision、すべてを止めるなら {continue: false})でブロックします。
  • 共通の出力フィールド — additionalContext、systemMessage、reloadSkills、sessionTitle、suppressOutput、updatedInput — を使えば、許可か拒否だけでなく Claude を誘導できます。
  • フックは高速・冪等・狭いマッチャーに保ち、自分で書いていないフックは必ず確認しましょう。シェルの権限で動くからです。

次へ​