Chapter 11

第11章: Hooks と harness ─ ループの外側で起こること

ある日のチーム Slack に、こんな投稿が流れる。

「PR レビューしてたら、any 型が 30 箇所増えてた。エージェントが書いた変更だ。 CI は通ってるんだけど、型チェック設定がゆるくて素通り…どうにかしたい」

permissions (第10章) で npm installrm -rf は止められた。 でもこの問題は止めるべき道具がない。普通の Edit で書き込んだだけだから。

求めているのは「書き込み自体は許す。でも書き込んだ後に必ず型チェックを走らせて、ダメなら差し戻す」── 道具呼び出しの 前後にシェルを差し込みたい という要求だ。 これを叶えるのが本章の主役 hooks だ。

11.1 そもそも、permissions では足りないこと

第10章で見た permissions は、ツール呼び出しが起きるか起きないか を決める仕組みだった。 allow / ask / deny の三段階。とてもシンプルで、それ自体は強い。

ところが現場の要求は、もっと細かい:

これらは「許す / 許さない」では表現できない。 ツールのライフサイクルのある瞬間に、エージェントとは別のロジックを差し込みたい という要求だ。

permissions は ゲート (門)。hooks は タイミング駆動の差し込み。 門は通すか止めるかだけだが、hooks は通すついでに何かを実行できる。

両者は競合せず補完関係にあり、Claude Code はその両方を持っている。

11.2 hooks の正体 ── ハーネスがシェルを実行する仕組み

第1章で見たとおり、Claude Code の本体は ハーネス (harness) という外側のプログラムだ。 LLM はテキストを返すだけで、本当にファイルを書いたりシェルを実行したりするのはハーネスの仕事。

ハーネスは LLM の出力を見て、こんな順で動いている:

  1. LLM の出力に「Edit ツール呼び出し」が混じっているのを検出
  2. permissions をチェック (allow なら通す)
  3. ここで一拍置いて、登録された PreToolUse hook があれば実行
  4. 本当に Edit ツールを動かす (ファイルを書く)
  5. PostToolUse hook があれば実行
  6. 結果を LLM に観測として戻す

このサンドイッチ部分が hooks だ。

hooks

ツール呼び出しやセッション境界などの 特定のタイミング で、ハーネスが自動的に実行するシェルコマンド。LLM とは独立に動く。pre / post の前後はもちろん、SessionStart や UserPromptSubmit などライフサイクルの様々な地点に差し込める。

ここで決定的に重要な点を一つ。

hooks は LLM が知らないうちに副作用を起こせる ── これは強力だが、危うさも持つ。

LLM 自身は「自分の Edit のあとに formatter が走った」「commit が裏で中断された」ことを、観測として返されない限り知らない。 便利の裏で、hooks が何をしたかの監査ログを残さないと、エージェントの挙動が再現できなくなる ことがある。差し込みの強さは、追跡しやすさとのセットで設計する必要がある。

11.3 ライフサイクルのどこに刺さるか

hooks が差し込める地点は、ツール呼び出しの前後だけではない。 セッション全体のライフサイクルに、複数の刺し込み点が用意されている。

主要なものを並べておく:

Hookタイミング典型的な用途
SessionStartセッション開始時環境変数読み込み、ブランチ情報の注入
UserPromptSubmitユーザーがプロンプトを送る直前社内ガイドラインの注入、機密情報のフィルタ
PreToolUseツール呼び出しの直前型チェック、lint、変更ファイルのバックアップ
PostToolUseツール呼び出しの直後formatter 実行、変更通知の送信
Stopエージェントのループ停止時 (LLM がツール呼び出しを含まない出力を返してループが終わる瞬間、または max_turns 上限に達した瞬間)結果サマリー出力、Slack 通知
時間 →SessionStarthookユーザー入力”これ直して”UserPromptSubmithookLLM 思考Edit を呼ぼうPreToolUsehook (型チェック)Edit 実行PostToolUsehook (formatter)LLM 観測続きを考えるStop hook(終了時)
図 11.1 — エージェントのライフサイクルと hooks の刺し位置。上段が hooks、下段が通常のエージェント処理。

ポイントは、hook はそれぞれ 独立したシェル実行 で、戻り値 (exit code) や標準出力によって 後続の処理を止めたり、context に内容を注入したり できることだ。

たとえば PreToolUse hook が exit code 非ゼロを返せば、ハーネスは そのツール呼び出しを中止 する。 これが冒頭の「commit 前に lint を走らせて、失敗なら commit を止めたい」を実現する仕組みだ。

11.4 settings.json で宣言的に書く

hooks は コードで実装するものではなく、settings.json に宣言的に書く ものだ。

仕組みのイメージはこう (構造を理解するための擬似コード。実構文は第Ⅱ部「コマンド: 拡張機能」の /hooks で詳述):

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash(git commit:*)",
        "command": "pnpm lint && pnpm typecheck"
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "command": "pnpm format"
      }
    ]
  }
}

宣言的に書くことの嬉しさは大きい:

宣言的設定 (declarative configuration)

「何をするか」だけを書き、「どうやるか・いつやるか」はハーネス側が解釈する書き方。 命令的に「if 〜 then 〜」のコードを書く代わりに、「これに該当するときはこのコマンドを実行」という対応表を並べる。settings.json の hooks はこの形を取っている。

permissions と同じく、hooks 設定にも プロジェクト / ユーザー / セッション の階層がある。 チーム共通の規律はプロジェクトに、個人の好みはユーザーに ── という分け方は permissions と一致する。

11.5 二つの使い方 ── 自動化 (生産性) と規律 (安全)

hooks の用途は、大きく二つに分けられる。

使い方
自動化 (生産性)PostToolUse で formatter を毎回走らせる、Stop で Slack 通知
規律 (安全)PreToolUse で型チェック、UserPromptSubmit で機密情報フィルタ

自動化の方向は「毎回手で打つのを面倒くさいから自動で」という生産性の動機。 規律の方向は「LLM が忘れても見落としても、必ずこれは実行する」という安全の動機。

hooks の真価は、後者 (規律) のほうにある

生産性は「便利」なだけで、無くても作業は進む。 だが規律は LLM のハルシネーションや忘却に対する 物理的な砦 であり、これがあるからこそ「賢いが完璧ではない」LLM を信用してタスクを任せられる。

第10章の permissions が「危ない道具を止める門」だとすれば、hooks は「通る人全員に必ず実行させる手続き」だ。 二つを組み合わせると:

両者の合わせ技で、エージェントを安全に走らせる土台ができる。

11.6 危うい使い方 ── hooks で勝手にコミットする

便利な道具には誤用がある。よくある危うい例:

Stop hook で 「最後に必ず変更をコミットして push する」 を仕込む

便利そうに見える。ところがこれをやると:

落とし穴

hooks は 強力すぎる道具 なので、第10章の「reversibility × blast radius」軸で考えるクセを失わない。 取り返しのつかない操作・広いブラスト半径を持つ操作を hooks に仕込むと、LLM のハルシネーションをハーネス側が自動増幅する ことになる。

良い hooks の指標は「失敗が起きても作業者本人で取り返せる範囲か」。 formatter、lint、型チェック、ローカル通知 ── ここまではよい。push、merge、外部送信 ── これらは hooks の仕事ではない。

11.7 制御編の総括 ── ハーネスがエージェントを御するための五つの道具

ここで第7〜11章を一段引いて見渡しておく。制御編で扱った 5 つは、すべて ハーネスがエージェントを御するための道具立て だった。

道具何を御するか
第7章計画 (Plan / TODO)思考の発散と段取り
第8章記憶 (CLAUDE.md / memory)セッションを越えた知識の継承
第9章サブエージェント (Task)コンテキストの肥大
第10章権限 (permissions)道具の通過判定
第11章hooks道具の前後への差し込み

これらに共通するのは、どれも LLM 自身の中ではなく、ハーネス側のレイヤーに住んでいる ことだ。計画は TODO リストとして外に置かれ、記憶はファイルとして外に書かれ、サブエージェントは Task ツールとしてハーネスが起動し、permissions と hooks はそもそも LLM の出力を観測するハーネスの仕事。

第1章で予告した「賢さの大半はハーネスが担う」の中身は、つまりこの 5 つの道具立てのことだった ── という総決算がここで取れる。

そして制御編で扱ったのは、すべて 閉じた世界の中の話 だった。Claude Code が持つ道具・記憶・権限・hook ── どれも箱の中で完結している。

次章からは扉を開ける。ここまでは閉じた世界。次は外の世界とつなぐ。 ローカルファイルではない外部システム、別プロセスとして提供されるツール群、サードパーティのサービス ── これらをエージェントの道具にする仕組みが、次章の MCP だ。

11.8 この章の振り返り

この章で読めるようになるツイート / ブログ

次章では、ハーネスへの拡張のうち、もう一段大きな仕組みを扱う ── 外部のサーバーを道具として取り込む 規格、MCP (Model Context Protocol) だ。 hooks がローカルの差し込みだとすれば、MCP は道具セットそのものを外から差し替える話になる。