はじめに
AI コーディングエージェント(Claude Code など)にプロジェクト固有のルールを覚えさせる仕組みとして CLAUDE.md がある。リポジトリ直下へ置くと、エージェントが毎回それを読んでから作業してくれる「プロジェクトの憲法」のようなファイルだ。/clearコマンド後も毎回読み込むことを今回初めて知った。よく考えたら、そりゃそうなんだが。
ところがこのファイル、運用しているうちにどんどん太る。私のプロジェクトでは、最初に書いたときは 200 行程度だったものが、気づけば 500 行を超えていた。ルールを追加するたびに「これも書いておこう」が積み重なった結果だ。
本記事では、
- 肥大化した
CLAUDE.mdをスリム化した話と、そのメリット・デメリット - そもそも
CLAUDE.mdには「やってはいけないことを止める力」がないという限界 - それを補う フック(hook) とは何かの深掘りと、実際の活用方法
を、図解を交えて解説する。AI エージェントを本格運用している人なら、規模を問わず刺さる話だと思う。
第1部CLAUDE.md が太る問題
なぜ太るのか
CLAUDE.md は「毎ターン、コンテキストの先頭に丸ごと読み込まれる」という性質を持つ。つまり、
- エージェントが 1 回応答するたびに、全文がトークンとして消費される
- 長くなるほど、毎回のコストと処理時間が増える
- そして長文になるほど、肝心のルールが埋もれて効きにくくなる(いわゆる "lost in the middle")
にもかかわらず、運用中は「ルールを足す」方向の圧力しかかからない。バグを踏むたび、事故を起こすたびに「次から気をつけるためのルール」を追記していく。削る動機がないので、単調増加する。
行数
500 ┤ ╭────── ← 気づいたらここ
│ ╭─────╯
400 ┤ ╭──────╯
│ ╭──────╯
300 ┤ ╭──────╯
│ ╭─────╯
200 ┤───╯ ← 最初はこのくらい
└────────────────────────────────────────▶ 時間
「これも書いておこう」の積み重ね
太った CLAUDE.md の何が問題か
| 問題 | 中身 |
|---|---|
| 毎ターンのトークン浪費 | 全文が毎回コンテキストに載る。500 行 ≒ 30KB を毎回読む |
| ルールの希薄化 | 重要な禁止事項が、細かい手順書に埋もれて目立たなくなる |
| 重複・ドリフト | 別ドキュメントに正本があるのに本文へ丸写し → 片方だけ更新されて食い違う |
| 人間も読まない | 長すぎて、メンテする人間自身が全体像を把握できなくなる |
第2部スリム化 ── やったことと得失
方針:「憲法」と「六法全書」を分ける
スリム化の core は単純で、CLAUDE.md は "毎回守るべき規律+詳細の在処の索引" に絞り、詳細は別ドキュメントへ追い出すこと。
具体的には、
- worktree のクリーンアップ手順・コマンド集 → 運用手引きドキュメントへ
- フックのセットアップ手順(初回 1 回しかやらない)→ セットアップ手引きへ
- DB 設計原則の全文・コード例・例外規定 → アーキテクチャドキュメントへ
を切り出し、CLAUDE.md 側には「これは○○.md を見ろ」という 1 行リンクだけを残した。結果、540 行 → 201 行(約 60% 削減)。
メリット
- 毎ターンのトークンが減る — 直接的にコスト減・レスポンス高速化。地味だが毎回効く
- 重要ルールが目立つ — ノイズが減り、禁止事項やフローが埋もれなくなる。エージェントの遵守率が上がる方向
- 必要なときだけ詳細を読む — 詳細は「リンクを辿った時」にだけコンテキストへ載る(オンデマンド読み込み)
- 人間がメンテしやすい — 全体を一目で見渡せるサイズに戻る
- 重複の解消 — 正本を 1 箇所に集約でき、ドリフト(食い違い)の温床が減る
デメリット(ここが重要)
スリム化は無料ではない。トレードオフがある。
- 情報が分散する — 詳細は別ファイル。エージェントがリンクを辿らなければその詳細は知り得ない。サブエージェントなど、リンク先まで読みに行かないケースで取りこぼす恐れ
- 要約による情報欠落 — 「7 原則を 1 行ずつ」に畳むと、ニュアンスや例外が落ちる。曖昧さが増す
- 二重管理のリスク — 「本文の 1 行要約」と「リンク先の全文」の 2 ソースになり、放置すると再びドリフトする
- 再編成そのもののリスク — 切り出し作業中に、ルールを 1 つ取りこぼす事故が起こりうる
教訓:スリム化の判断基準は「それは毎回必要か?」。毎ターン全エージェントが守るべき規律は本文へ。実装着手時・初回セットアップ時にだけ読む詳細は外部化してリンク。この線引きが肝。
第3部CLAUDE.md の決定的な限界
ここからが本題だ。スリム化で「読みやすさ」と「コスト」は改善した。だが、運用で本当に困っていたのは別の問題だった。
「main で作業するな」が守られない
私の運用ルールでは、開発・修正は必ず隔離環境(git worktree)で行い、本陣(メインの作業ディレクトリ)では直接コーディング・コミットしてはいけないことになっている。複数のエージェントセッションを並行で走らせると、同じディレクトリで git checkout がぶつかり、作業中のファイルが消える事故が起きるからだ。
これを CLAUDE.md に何度も太字で書いた。にもかかわらず、ルールを破るエージェントが多発した。
なぜか。答えは身も蓋もない。
CLAUDE.md は「お願い」であって「強制」ではないから。
ドキュメントによる制御は、必ず「読み手が読んで・理解して・従う」という 3 段階の善意に依存する。LLM は確率的に振る舞うので、
- 長いコンテキストでルールが希薄化して見落とす
- サブエージェントにはそもそもルールが十分に伝わっていない
- 「今回くらいは」と判断がぶれる
といった理由で、いつかは破られる。お願いベースの制御は、100% にはならない。
これは AI に限った話ではない。人間のチームでも「コーディング規約に書いてあるのに守られない」は永遠の課題だ。規約(ドキュメント)と、それを機械的に強制する仕組み(CI・リンタ・フック)は別物なのである。
第4部フックとは何か
そこで登場するのが フック(hook) だ。
フックの一般的な定義
フックとは、「あるシステムの処理の流れの中の、決まったタイミング(イベント)に、自分の処理を割り込ませる仕組み」 のことである。
「釣り針(hook)」が語源で、システムが用意した"引っ掛け穴"に、自分のコードを引っ掛けるイメージ。プログラミングの世界では至るところに存在する。
| 例 | どのイベントに割り込むか |
|---|---|
| git フック | commit する直前・push する直前 など |
| Webhook | 外部サービスでイベントが起きた瞬間(PR が作られた等)に通知が飛ぶ |
| シェルのフック | コマンド実行の前後 |
| エディタのフック | ファイル保存時にフォーマッタを走らせる |
共通する構造はこうだ:
ドキュメントとの決定的な違いは、フックはシステム自身が機械的に実行すること。実行する主体(人間か AI か、善意か悪意か、注意深いか不注意か)に一切依存しない。これが「お願い」を「強制」に変える。
「お願い」 vs 「強制」
第5部実際にフックで守りを固める
私のケースでは、「本陣で作業させない」を 2 層のフックで機械的に強制した。
層A:git の pre-commit フック(最終防衛線)
git には標準で フックの仕組みが備わっている。.git/hooks/ に置いたスクリプトが、commit や push などの節目で自動実行される。pre-commit は名前の通り「commit が確定する直前」に走り、終了コードが 0 以外なら commit を中止できる。
ポイントは、これが git というシステムのレベルで効くこと。誰が・どのツールで commit しようとしても(人間でも AI でも、Bash でも別のツールでも)、必ずこのフックを通る。だから最終防衛線として最も堅い。
判定ロジックの肝はこうだ。git の worktree(隔離環境)では git-dir と git-common-dir が異なるが、本陣では一致する。これを使って「今いるのが本陣かどうか」を機械的に判定し、本陣なら拒否する。
#!/bin/sh
# pre-commit: 本陣での commit を禁止し、隔離環境(worktree)を強制する
git_dir=$(git rev-parse --absolute-git-dir 2>/dev/null)
common_dir=$(git rev-parse --path-format=absolute --git-common-dir 2>/dev/null)
# 本陣では両者が一致する → commit を拒否
if [ -n "$git_dir" ] && [ "$git_dir" = "$common_dir" ]; then
echo "X 本陣での commit は禁止です。worktree を使ってください。" >&2
exit 1 # 0以外を返すと commit が中止される
fi
exit 0
層B:AI エージェント側の PreToolUse フック(着手を阻止)
git フックは堅いが、commit の瞬間まで止まらないという弱点がある。本陣で git checkout してファイルを引っ張ってしまう事故(HEAD 衝突の主因)は、commit ではないので pre-commit では防げない。
そこで、AI エージェント(Claude Code)が持つ PreToolUse フックを使う。これは「エージェントがツールを実行する直前」に割り込めるフックで、PreToolUse のほかに PostToolUse(実行後)など、ライフサイクルの各イベントが用意されている。
このフックは判定結果として deny を返せる。deny が返ると、エージェントはそのツール呼び出しを実行できない。つまり commit に至る手前、git checkout を打とうとした瞬間に止められる。
設定はエージェントの設定ファイルに「どのイベントの・どのツールに・どのスクリプトを引っ掛けるか」を登録するだけ。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "powershell -File \".../block-honjin-git.ps1\"",
"if": "Bash(git *)"
}
]
}
]
}
}
スクリプト側は、
- これから実行されるコマンドが変更系の git 操作(checkout/switch/commit/reset/merge/rebase/cherry-pick/pull)かを見る
- worktree 配下なら素通り
- 本陣(
git-dir == git-common-dir)ならdenyを返す
という、層A と同じ判定を「着手段階」で行う。
なぜ 2 層にするのか
| 層 | いつ止まるか | 守備範囲 | 強み |
|---|---|---|---|
| 層B(PreToolUse) | ツール実行の直前 | エージェントの全 git 操作(checkout 等も) | 事故の手前で止まる |
| 層A(pre-commit) | commit 確定の直前 | あらゆる主体の commit | すり抜けを許さない最終防衛線 |
層B は守備範囲が広いが、エージェント側の設定なので「設定の漏れ」や「別ツール経由のすり抜け」がありうる。層A はタイミングこそ遅い(commit 時)が、git レベルで効くので誰がどうやっても確実。速い網(層B)と、漏らさない網(層A)を重ねることで、片方をすり抜けても他方で止まる。これがセキュリティでいう 多層防御(defense in depth) の考え方だ。
第6部ドキュメントとフック、それぞれの役割
最後に、両者は対立するものではなく役割分担だということを強調したい。
- ドキュメントが得意なこと:意図・背景・判断基準を伝えること。「なぜ worktree を使うのか」「どういう設計思想なのか」。こうした"考え方"はフックでは表現できない。
- フックが得意なこと:「これだけは絶対に許さない」という一線を、確実に止めること。特に破滅的・不可逆な操作(本番への誤操作、作業消失、秘密情報のコミットなど)のガードレール。
結論:守ってほしいことは CLAUDE.md に書く。守らせたいことはフックにする。お願いと強制を使い分けるのが、AI エージェント運用の勘所だ。
まとめ
-
CLAUDE.mdは毎ターン読み込まれるので太ると毎回コストがかかり、ルールも希薄化する。「毎回必要な規律」と「着手時だけ読む詳細」を分け、後者は別ドキュメントへ切り出してリンク化するのがスリム化の定石 - スリム化のメリットはコスト減・遵守率向上・保守性。デメリットは情報分散・要約欠落・二重管理リスク。トレードオフを理解して線引きする
-
CLAUDE.mdは「お願い」であり、強制力がない。守られない一線は、ドキュメントをいくら太字にしても守られない - フックは「処理の流れの決まったタイミングに自分の処理を割り込ませる仕組み」。システムが機械的に実行するので、実行主体の善意に依存せず強制できる
- git の
pre-commit(最終防衛線)と AI エージェントのPreToolUse(着手阻止)を多層で組み合わせると、片方をすり抜けても他方で止まる - ドキュメントとフックは対立せず役割分担。意図はドキュメント、一線はフックで
AI エージェントに任せる範囲が広がるほど、「お願い」だけでは事故は防げない。守らせたい一線にはフックを。