hooks の仕組み
/debug のライフサイクルでは、各段階で hook が割り込む様子を見ました。この章では、hook そのものの仕組み(どこに、どう登録され、どう作用するか)を整理します。
hook はイベントに紐づくスクリプト
hook は ~/.claude/hooks/ に置かれた実行可能なスクリプト(執筆時点で 12 本のシェルスクリプト。直後の事故対応で 5 本追加し、現時点では計 17 本まで育っています。経緯は本章末の :::info を参照)です。それを settings.json の hooks セクションでイベントに紐づけて登録します。なお、イベント体系と exit code の仕様は Claude Code 本体の提供(📦 標準)で、これらのスクリプトはすべて筆者の自作(🔧)です。
"hooks": {
"Stop": [
{ "matcher": "", "hooks": [
{ "type": "command", "command": "~/.claude/hooks/check-plain-question.sh" },
{ "type": "command", "command": "~/.claude/hooks/check-unverified-claim.sh" }
]}
]
}
同じイベントに複数登録すると、すべてが(公式の既定では並列に)実行されます。matcher を指定すると、対象のツール名などで絞り込めます(例: "Edit|Write")。
8 つのイベント
この環境では次の 8 イベントに hook を登録しています。Guides と Sensors の十字分類でいうと、Guide(行動前)と Sensor(行動後)が時間軸に分かれて並びます。
| イベント | タイミング | 登録例 | 役割 |
|---|---|---|---|
SessionStart | セッション開始時 | ensure-askuserquestion-loaded.sh / surface-subagent-results.sh | 準備の案内(Guide) |
UserPromptSubmit | 発話の送信時 | promote-ultracode.sh / surface-salvage-on-resume.sh | 重いタスクの誘導・再開時の案内(Guide) |
PreToolUse | ツール実行の直前 | check-protected-files.sh | 危険操作のブロック(Guard) |
PostToolUse | ツール実行の直後 | check-suppression.sh / save-subagent-result.sh / check-rate-limit-midturn.sh | 握り潰しの検出・成果物の即時保存(Sensor) |
PostToolUseFailure | ツールが失敗した直後 | salvage-agent-failure.sh | 失敗した subagent の途中ログ回収を案内 |
Stop | 応答確定の直前 | check-plain-question.sh など計 7 本 | 出力の多段検査(Sensor) |
PreCompact | コンテキスト圧縮の前 | precompact-snapshot.sh | 状態のスナップショット |
Notification | 許可待ち・アイドル時 | say-notify.sh | 音声通知 |
本章の初稿を書いた直後(同日午後)に、作業ターンの実行中に 5 時間レート上限へ直撃し、subagent が長時間かけた調査結果がセッションごと消える事故が起きました。再発防止として、その日のうちに save-subagent-result.sh / surface-subagent-results.sh / salvage-agent-failure.sh / surface-salvage-on-resume.sh / check-rate-limit-midturn.sh の 5 本を追加し、現時点の本数は 17 本となっています(本章前半の表に登録例として並んでいるのはこの 5 本を含む現時点のスナップショットです)。再発防止の設計は 2 段です。subagent の最終報告は完了の瞬間にディスクへ保存し(Sensor)、次のセッション開始や再開時に「保存済みの成果物がある」と気づける導線を注入します(Guide)。失敗を 1 回で済ませて仕掛けに変える、ハーネスの育ち方の実例です。
ensure-askuserquestion-loaded.sh も同型です。質問の構造化に使う AskUserQuestion ツールはスキーマが遅延ロードされる仕様のため、未ロードのまま呼ぶと失敗し、平文質問への退避を誘発していました。そこでセッション開始時に「先にスキーマをロードせよ」という案内を決定的に注入しています。
exit コードが振る舞いを決める
hook の作用は、終了コードで決まります。
exit 0: 問題なし。何もせず通過するexit 2: ブロックまたは stderr フィードバック。PreToolUseならツール実行をブロック、Stopなら応答を差し戻し、UserPromptSubmitでは prompt が破棄されます。一方PostToolUseはツールが既に実行された後なのでブロックはできず、stderr の内容のみがモデルにフィードバックされます- それ以外の exit コード: non-blocking エラー。stderr の先頭行がユーザー向けに表示され、実行はそのまま継続します
exit 2 の意味はイベントで少し変わります。PreToolUse ならそのツール実行を止め、Stop なら応答を差し戻して自己修正させます。PostToolUse は実行済みなのでブロックはできず、stderr が次のステップへのフィードバックとして渡るだけです。/debug のライフサイクルで見た「.env への書き込みブロック」「平文質問の差し戻し」は、どちらもブロックを伴う exit 2(前者は PreToolUse、後者は Stop)です。
echo "Detected: 平文質問パターンが検出されました..." >&2 # stderr に理由
echo "対応: AskUserQuestion tool で構造化された選択肢を提示してください。" >&2
exit 2 # 差し戻し
stderr に「何が問題で、どう直すか」を書いて exit 2 する。この一手で、モデルは自分の出力を修正できます。
hook は乱暴に止めるだけの仕掛けではありません。多くの Sensor には次の安全装置が入っています。
- log-only モード: 誤検出が読めないうちはブロックせず記録だけ(
check-unverified-claim.shなど)。測ってからブロックに昇格 - 緊急 override:
CLAUDE_HOOK_SKIP_PROTECTED=1のような環境変数での無効化を設計していた (のちの実機検証で、セッション内のexportは hook プロセスに継承されないと判明。恒久無効化なら CLI 起動前の export、単発の迂回なら marker file 方式が現実解) - 自己ループ防止: Stop hook は
stop_hook_activeを見て、自己修正の 2 回目以降は通過する
要点は次のとおりです。
- hook は
~/.claude/hooks/のスクリプトをsettings.jsonでイベントに登録したもの - この環境では 8 イベント(SessionStart から Notification まで)に hook を登録している。Claude Code 本体が提供する hook イベントは 2026-06 時点で約 30 種類あり、その一部を使っている
exit 0で通過、exit 2で差し戻し(stderr の内容がモデルへのフィードバックになる)- log-only・override・ループ防止といった安全装置が組み込まれている
次は MCP の仕組み で、ツールを必要時だけ読み込む 2 段ロードを掘り下げます。