第11章: Hooks と harness ─ ループの外側で起こること
ある日のチーム Slack に、こんな投稿が流れる。
「PR レビューしてたら、
any型が 30 箇所増えてた。エージェントが書いた変更だ。 CI は通ってるんだけど、型チェック設定がゆるくて素通り…どうにかしたい」
permissions (第10章) で npm install や rm -rf は止められた。
でもこの問題は止めるべき道具がない。普通の Edit で書き込んだだけだから。
求めているのは「書き込み自体は許す。でも書き込んだ後に必ず型チェックを走らせて、ダメなら差し戻す」── 道具呼び出しの 前後にシェルを差し込みたい という要求だ。 これを叶えるのが本章の主役 hooks だ。
11.1 そもそも、permissions では足りないこと
第10章で見た permissions は、ツール呼び出しが起きるか起きないか を決める仕組みだった。 allow / ask / deny の三段階。とてもシンプルで、それ自体は強い。
ところが現場の要求は、もっと細かい:
- Edit の 後 に必ず formatter を走らせたい
- Bash で
git commitする 前 に lint と型チェックを走らせ、失敗なら commit を止めたい - セッションが始まる 時 に最新の env を読み込みたい
- ユーザーがプロンプトを送る 直前 に、社内ガイドラインを差し込みたい
これらは「許す / 許さない」では表現できない。 ツールのライフサイクルのある瞬間に、エージェントとは別のロジックを差し込みたい という要求だ。
permissions は ゲート (門)。hooks は タイミング駆動の差し込み。 門は通すか止めるかだけだが、hooks は通すついでに何かを実行できる。
両者は競合せず補完関係にあり、Claude Code はその両方を持っている。
11.2 hooks の正体 ── ハーネスがシェルを実行する仕組み
第1章で見たとおり、Claude Code の本体は ハーネス (harness) という外側のプログラムだ。 LLM はテキストを返すだけで、本当にファイルを書いたりシェルを実行したりするのはハーネスの仕事。
ハーネスは LLM の出力を見て、こんな順で動いている:
- LLM の出力に「Edit ツール呼び出し」が混じっているのを検出
- permissions をチェック (allow なら通す)
- ここで一拍置いて、登録された PreToolUse hook があれば実行
- 本当に Edit ツールを動かす (ファイルを書く)
- PostToolUse hook があれば実行
- 結果を 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 通知 |
ポイントは、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"
}
]
}
}
宣言的に書くことの嬉しさは大きい:
- 読みやすい ── 「いつ何が走るか」が一目で分かる
- 共有しやすい ── settings.json をリポジトリに入れれば、チーム全員に同じ規律が効く
- LLM が改変しづらい ── コードと違って、エージェントが勝手にコードを書き換えて hook を無効化することがしにくい
宣言的設定 (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 は「通る人全員に必ず実行させる手続き」だ。 二つを組み合わせると:
- permissions ── 「
git push --forceは通さない」 - hooks ── 「通る
git commitには必ず lint を走らせる」
両者の合わせ技で、エージェントを安全に走らせる土台ができる。
11.6 危うい使い方 ── hooks で勝手にコミットする
便利な道具には誤用がある。よくある危うい例:
Stop hook で 「最後に必ず変更をコミットして push する」 を仕込む
便利そうに見える。ところがこれをやると:
- 失敗作業も中途半端な変更もすべて記録される
- レビュー前の変更が共有 branch に流れる
- 「誰が何のために commit したか」の意図が消える
- 戻すのが格段に難しくなる (第10章の reversibility 観点)
落とし穴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 この章の振り返り
- permissions は「ツールを通すか止めるか」、hooks は「通す前後でシェルを差し込む」仕組み
- hook は LLM の判断を介さず、ハーネス側で機械的に実行される
- 刺し込み地点は SessionStart / UserPromptSubmit / PreToolUse / PostToolUse / Stop など
- exit code 非ゼロを返せば後続処理を 止められる (lint 失敗で commit を止める等)
- settings.json に 宣言的 に書く。プロジェクト / ユーザー / セッションの階層あり
- 用途は 自動化 (生産性) と 規律 (安全) の二面。真価は後者
- 危うい使い方: 取り返しのつかない操作を hooks に仕込むと事故を自動増幅する
- 制御編の総括: 計画 / 記憶 / サブエージェント / 権限 / hooks の 5 つは、ハーネスがエージェントを御するための道具立て。次章からは外の世界へ
この章で読めるようになるツイート / ブログ
- 「PreToolUse で
pnpm typecheck仕込んだら、anyを入れる PR が激減した」 → 規律方向の典型例。LLM の意思に関わらずハーネスが型を守る、と読める - 「Stop hook で自動 commit してたら branch が荒れた、戻した」 → 取り返しのつかない操作を hooks に仕込んだ典型事故。本章 11.6 そのもの
- 「PostToolUse で formatter 流すだけで作業ストレス激減」 → 自動化方向。生産性の hook の代表例
- 「permissions だけだと足りない、hooks 入れて初めて運用が回った」 → 二つの仕組みが補完関係であることを実感したケース
次章では、ハーネスへの拡張のうち、もう一段大きな仕組みを扱う ── 外部のサーバーを道具として取り込む 規格、MCP (Model Context Protocol) だ。 hooks がローカルの差し込みだとすれば、MCP は道具セットそのものを外から差し替える話になる。