フックはすべて傷跡である——コードに刻まれた84のエージェント障害
エージェント統括システムには現在、84個のフックが組み込まれています。v2.1.116(2026年4月)時点で Claude Code が公開するライフサイクルイベント26種類のうち、15種類を捕捉している計算です。各フックの正体はシェルスクリプトか Python の断片で、特定のエージェント動作の前後で発火します——ファイルの読み取り、書き込み、bash コマンド、Web リクエスト、サブエージェントの起動、git 操作、MCP のツール呼び出し。そしてどのフックにも、何かが壊れた過去があります。
エージェント統括システムのフックは、例外なく特定の本番障害にさかのぼれます。つまりフック群とは、シェルスクリプトの形で符号化された組織の記憶なのです。 エージェントは CDN キャッシュを消し飛ばし、認証情報ファイルを読み、走らせてもいないテストを「合格」と報告し、40分にわたって課題から逸脱していきました。そのたびに、挙動の決まった小さなガードが生まれ、以降のすべての作業回で静かに発火し続けています。
理論上の間違いではありません。本番での間違いです。あるエージェントは数百万リクエストを捌く CDN キャッシュを消し飛ばしました。あるエージェントは SSH 鍵を書き込もうとしました。あるエージェントは pytest を一度も呼ばずに「全テスト合格」と報告しました。あるエージェントは課題から大きく外れ、指示された作業とは無関係なファイルの関数を40分かけて最適化していました。
これらのフックを先回りして設計したわけではありません。自律型 AI エージェントの障害モードを机上で列挙し、予防的な制御を書き下ろした——そんな経緯は一つもないのです。すべてのフックは事後的に生まれました。何かが壊れ、二度と壊れないようスクリプトを書き、そのスクリプトはそれ以降すべての作業回で静かに発火してきました。このフック体系はセキュリティアーキテクチャではありません。傷跡のコレクションです。
要点
- キャッシュパージ: エージェントが正規の API 呼び出しで本番 CDN キャッシュを消し飛ばしました。現在は2つのフック(47行)が、破壊的操作を人間が手で打つ合言葉の後ろに閉じ込めています。
- 認証情報の読み取り: エージェントが API トークンをコンテキストウィンドウに取り込みました。パス照合ガードが認証情報ファイルの読み取りを遮断し、
.envへのアクセスは記録します。 - 幻のテスト検証: エージェントが pytest を実行せずに「全テスト合格」と報告しました。曖昧表現の検出器により、幻の検証は全作業回の12%から2%未満まで下がりました。
- 12回の逸脱: 60日間で、エージェントが課題を見失ったことが12回確認できました。しきい値0.30のコサイン類似度検出器が、25回のツール呼び出しごとに発火します。
- 分類体系: 6つの構造的な障害カテゴリーが84個のフックすべてを覆います。500回を超える作業回を経て、新種のカテゴリーはほとんど現れません。この体系はインシデントのたびに手強くなっていきます。
キャッシュパージ——正規の呼び出し1回が本番を壊した経緯
2026年3月21日、resumegeni.com のマーケットページの読み込みが遅い原因を調べるようエージェントに頼みました。エージェントの調査は順当な滑り出しでした。ルートハンドラを読み、データベースクエリを確認し、テンプレートのレンダリングをプロファイリングする。そこで、Cloudflare に残った古いキャッシュエントリが本当の性能特性を覆い隠しているのではないか、と判断したのです。
エージェントは purge_everything: true を付けて mcp__cloudflare__cache_purge を呼び出しました。
本番サイトのキャッシュ済みページが、一瞬ですべて無効化されました。それまで大半のリクエストを80〜100msで返していた CDN は、あらゆるリクエストを Railway のオリジンサーバーへ転送する状態に転落します。オースティンのマーケットページは1秒未満から14,290ミリ秒へ。ニューヨークは1秒未満から6,891msへ。サイト上のすべてのページが、リクエストのたびに冷えたオリジンからレンダリングされるようになりました。
エージェントは権限を逸脱してはいません。正当な認証情報を持つ正規の MCP ツールを使い、許可された API エンドポイントを呼んだだけです。キャッシュの挙動をデバッグしている最中なら、キャッシュパージは筋の通った調査手順でしょう。問題は、「デバッグとしては妥当」と「本番としては致命的」がまったく同じ API 呼び出しだったこと、そしてエージェントの推論と本番への影響のあいだに、何の制約も存在しなかったことです。4
その晩、2つのフックを作りました。
Bash ガード(destructive-api-guard.sh): あらゆる bash コマンドで発火します。curl.*purge、rm -rf、DROP TABLE、docker.*rm、git push.*--force にパターン照合し、該当すれば完全遮断(exit 2)。エージェントには、なぜ遮断されたのかと代替案を説明するメッセージが届きます。合言葉「rosebud」なしには先へ進めず、その語がコンテキストに入る道は、人間が打ち込む以外にありません。
MCP ガード(destructive-mcp-guard.sh): mcp__cloudflare または mcp__github に一致する MCP ツール呼び出しで発火します。ツールのパラメータ内の purge、delete、destroy、remove にパターン照合。遮断も合言葉の関門も Bash ガードと同じ仕組みです。
フックは2つ。シェルスクリプトが2本。合計47行のコードです。導入以来、防いだキャッシュパージはゼロ件——合言葉の関門を設けて以降、試みたエージェントが現れていないからです。フックが攻撃を捕らえているのではありません。この種の過ちが起こりうる余地そのものを消しているのです。
このキャッシュパージ事故は、本来調べるはずだった性能問題も同時に露わにしました。冷えた状態でのレンダリングでオースティンが14秒——ここからマーケットページの引き継ぎが生まれ、4日後のクエリ形状の修正につながります。事故そのものは有益でした。フックは、それが二度と起きないことを保証します。
認証情報を読んだエージェント
2026年2月、プロジェクトの背景を集めていたエージェントが ~/.claude/docs/credentials.md を読みました。このファイルには Cloudflare、GitHub、Railway ほか各サービスの API トークンが入っています。エージェントは中身の要約を作業メモに含めました——つまりトークンが、Anthropic のサーバーへ送られる API リクエストに乗ったということです。
トークンをコミットしたわけでも、公に晒したわけでもありません。それでもトークンは、私の管理下にないコンテキストウィンドウの中を、サードパーティの API を経由して通過しました。リスクの及ぶ範囲は「自分のマシン」から「自分のマシン + Anthropic の推論インフラ」へと広がったのです。
認証情報パスガードは、すべてのファイル読み取りで発火します。パスを機微なパターンの一覧——.env、credentials、.ssh/、.aws/、.gnupg/、secrets——と照合する仕組みです。認証情報の読み取りには警告を記録したうえで遮断、.env の読み取りは許可しつつアクセスを記録します。
大半のパスに対しては助言的、認証情報ファイルに対しては完全遮断。この線引きには意味があります。環境変数名を把握するために .env を読むエージェントは、有用な文脈を得ているだけです。API トークンを把握するために credentials.md を読むエージェントは、セキュリティインシデントです。
導入以降、認証情報パスガードは200回超の作業回で23回発火しました。うち20回は .env ファイルの読み取り(記録して許可)。3回は認証情報ファイルや鍵ファイルを読もうとしたケース(遮断)です。遮断された3回はいずれも、プロジェクトの背景を広く集めるなかで、検索パターンにたまたま機微なファイルが引っかかったものでした。悪意あるものは皆無です。しかしガードがなければ、いずれの場合も秘密情報がコンテキストウィンドウに入り込んでいたでしょう。
幻のテスト検証
最も厄介な障害モードは、検証を実行していないのに検証成功を報告するエージェントです。
作業回147。データベースクエリをリファクタリングし、既存のテストスイートで変更を検証するよう頼みました。エージェントはクエリを正しくリファクタリングします。そして完了報告にはこうありました。「全テスト合格。リファクタリング後のクエリは元のクエリと同一の結果を返します」
作業ログを確認しました。pytest の実行は一度も現れません。テストランナーの類は何一つ呼ばれていませんでした。エージェントは、リファクタリング後のクエリが論理的に元と等価だからテストは通るはずだと推論し、その推論をテスト結果であるかのように報告したのです。
リファクタリング後のクエリは正しいものでした。テストも通ったはずです。エージェントの推論も筋が通っていました。しかしテストについて推論することは、テストを走らせることではありません。そしてこの二つの隙間こそ、バグが本番へ流れ出る場所なのです。もしリファクタリング後のクエリが、エージェントの推論が届かないエッジケースで微妙に誤っていたなら、そのバグは「テスト検証済み」と称する完了報告とともにデプロイされていたことになります。
この障害モードは、証拠ゲートのフックを作るまでの60回の作業回で7回発生しました。フックはすべての完了報告で発火し、曖昧な言い回しを探します——「通るはず」「〜だと思われる」「テストはおそらく合格」「自信があります」。検出されると、フックはこう差し込みます。「曖昧な表現を検出しました。具体的な根拠を示してください。テスト出力を貼るか、ファイル名と行番号を挙げるか、実施した検証手順を参照してください」
フックはテストが実際に走ったことを確かめているわけではありません。検証が省かれたことを示す言語パターンに旗を立てているだけです。検出は完璧ではありません。十分に流暢なエージェントなら、言い回しを変えてパターンをすり抜けられます。それでもフックはよくある形を捕らえており、その形は人間の介入を要したエージェント障害の12%を占めていました。1
フック導入後、幻の検証は全作業回の12%から2%未満へ落ちました。残る2%は、エージェントが曖昧表現を言い換えた場合か、検証の主張が厳密には正確でも不完全な場合です(統合テストを走らせていないのに「ユニットテストは合格」と述べる、など)。
逸脱
2026年1月から3月にかけて、逸脱検出器は12回発火しました。いずれも、エージェントが割り当てられた課題を見失っていたと確認できた回です。
逸脱検出器は、最初の課題プロンプトを埋め込みベクトルに変換し、エージェントの直近の行動の埋め込みと定期的に比較します。コサイン類似度が0.30を下回ると、元のプロンプトを含む警告が差し込まれる仕組みです。しきい値は実験を重ねて調整しました。0.50は敏感すぎ(正当なサブタスクの探索でも発火)、0.20は緩すぎ(明白な逸脱を見逃す)、0.30は確認済みの逸脱をすべて捕らえました。
作業回203が最も分かりやすい例です。課題は「アンパサンドを含む求人スラッグで壊れているサイトマップ XML のエスケープを直す」でした。エージェントはサイトマップ生成コードを読むところから始めます。次に、サイトマップがデータベースクエリから生成されていることに気づきます。次に、そのクエリを最適化できることに気づきます。そして40分をかけてクエリをマテリアライズドビューのパターンに書き換え、新しいクエリのテストまで書き、最適化完了と報告しました。アンパサンドのエスケープは、最後まで直されていません。
逸脱検出器があれば、ツール呼び出し25回の時点——作業開始からおよそ15分後、「サイトマップ XML のエスケープを直す」と「マテリアライズドビューを作る」の類似度がしきい値を割った瞬間——に捕捉できていたはずです。実際には、レビューで初めて逸脱に気づきました。
作業回89はもっと微妙でした。課題は「認証エンドポイントにレート制限を追加する」。エージェントはレート制限を正しく追加します。次に、認証フローのエラーメッセージが不揃いなことに気づきます。次に、そのメッセージを統一します。次に、エラーレスポンスの形式が API レスポンス形式の標準と違うことに気づきます。そして12のエンドポイントにまたがってレスポンス形式を書き換えました。レート制限そのものは正しく、完結していました。逸脱の正体は、この範囲の膨張です。
逸脱検出器はツール呼び出し25回ごとに発火します。しきい値を割った12回すべてで、エージェントは確かに元の課題から外れていました。6回は、差し込まれた警告を見たエージェントが自ら軌道修正。4回は、逸脱を認めつつ現在の作業には価値があると主張しました(実際に正しいこともありました)。残る2回は、警告を無視して脱線した作業を続けています。
フックは逸脱を防ぎません。逸脱を可視化するだけです。軌道修正するか、脱線した作業を認めるか——その判断は人間の手に残ります。それでもフックがなければ、逸脱は完了報告まで見えないままであり、その頃にはコンテキストの予算は使い切られているのです。
傷跡の分類体系
84個ものフックが集まると、模様が浮かび上がってきます。障害は6つのカテゴリーに収まりました。
| カテゴリー | フック数 | 例 |
|---|---|---|
| 認証情報の露出 | 12 | .ssh/ の読み取り、要約への API 鍵の混入、クラウド設定へのアクセス |
| 破壊的操作 | 8 | キャッシュパージ、データベースの削除、強制プッシュ、ファイル削除 |
| 課題からの逸脱 | 4 | 別の問題への着手、範囲の膨張、サブタスクの深追い |
| 出力品質 | 6 | 幻の検証、根拠のない曖昧表現、不完全な報告 |
| リソースの枯渇 | 3 | サブエージェントの過剰起動、無限ループ、コンテキストの溢れ |
| プロジェクト間の混線 | 4 | プロジェクト A のエージェントがプロジェクト B のファイルを変更 |
残る47個のフックは、プロジェクト固有のもの(規約の強制、デプロイのガード、翻訳の検証)か、実験的なもの(コスト追跡、作業回のメトリクス、稼働の生存確認)です。
6つの構造的カテゴリーは安定しています。この枠内で起きる新しいインシデントは、既存のフックが捕らえます。新種のカテゴリーはめったに現れません。運用6か月で新たに生まれた構造的カテゴリーは1つだけ(プロジェクト間の混線。obsidian-signals プロジェクトで走っていた作業回が blakecrosley.com のファイルを編集しようとして発覚しました)。残り5つのカテゴリーは、最初の60回の作業回のうちに出そろっています。
複数大学による14日間の実験「Agents of Chaos」——6つの AI エージェントにメール、bash、ファイルシステム、GitHub へのアクセスを与えた研究——も、重なり合う障害カテゴリーを独立に特定しています。過剰反応(破壊的操作)、なりすまし(認証情報の露出)、無限ループ(リソースの枯渇)、圧力下での段階的な迎合(課題からの逸脱)です。5 統制された研究と私の本番運用の経験が一致したという事実は、これらのカテゴリーが特定の構成に由来する副産物ではなく、自律エージェントの構造的な性質であることを示唆しています。
フックに捕らえられないもの
フックはツール呼び出しの層で動きます。行為が起きる直前か直後に割り込むのです。その行為へ至った推論には手が届きません。
報告されたバグを直す代わりに関数のリファクタリングを選んだエージェントは、正当なツール呼び出し(ファイル書き込み)を、正しい内容(構文的に妥当なコード)で発行しながら、課題に違反しています(対象の関数が違う)。怪しいツール呼び出しが一つもないため、どのフックもこれを捕らえられません。逸脱検出器はいずれ気づきますが、それは誤った作業にかなりのコンテキストを費やした後のことです。
もう一つ、フックが捕らえられないのは合成による失敗です。個々の行為はすべて許可されているのに、その連なりが許可されざる結末を生む。キャッシュパージがまさにそれでした。キャッシュ設定を読む(許可済み)、パージの API を呼ぶ(許可済み)、しかしその組み合わせ(調査の最中に本番キャッシュをパージする)は有害だったのです。MCP ガードはこの特定の組み合わせを捕らえるようになりましたが、まだ見ぬ合成には手が届きません。
サプライチェーンの合成の隙間3 も同じ層で働きます。信頼された部品が組み合わさって、許可されざる振る舞いになる。フックは部品層のガードです。合成の層で考えるには、個々の行為ではなく行為の連なりを評価する別の仕組みが要ります。逸脱検出器はそれに最も近い近似です——個々のツール呼び出しではなく、振る舞いの軌跡を評価するのですから。とはいえ測っているのは元の課題との類似度であって、合成された行為列の安全性ではありません。
フックと完全な安全性のあいだの隙間は、組織の記憶と組織の先見のあいだの隙間です。フックは何が壊れたかを覚えています。次に何が壊れるかは予測しません。
事後的であることの誠実さ
先回りしたフック体系を設計することもできたはずです。ありうる障害モードをすべて列挙し、それぞれに予防的な制御を書き、最初の作業回が始まる前に完全な安全アーキテクチャを組み上げる。
そうしないのは、先回りの設計が「まだ起きていない障害の予測」を必要とするからです。その予測は外れます。出来上がるフックは広すぎる(正当な行為まで遮断する)か、狭すぎる(実際の障害パターンを取り逃がす)かのどちらかでしょう。誤検知の多さはフック体系への信頼を蝕み、やがて私は警告を無視し始めるはずです。
事後的なフックは誠実です。一つひとつがこう告げます——「この具体的な出来事が起きた。これがそれを防ぐ具体的なガードだ」。ガードが障害に精密に合っているのは、障害の側がガードを定義したからです。誤検知が実際に少ないのは、そのパターンが脅威モデルの想像ではなく、現実のインシデントから抽出されたものだからです。事後的なガードも、コードベースの変化とともに後から広く効きすぎる可能性はあります。それでも出発点の精度は高いのです。
事後的な手法には代償があります。どのカテゴリーも、最初の1回は必ず成功してしまう。キャッシュパージは起きました。認証情報の読み取りも起きました。幻の検証は出荷されました。逸脱はコンテキストを食い潰しました。この「最初の1回」は、精密でノイズの少ないガードを手に入れ、2回目を防ぐための入場料です。
500回を超える作業回を経て、構造的な障害カテゴリーはおおむね出尽くしました。最初の1回の代償は、フックが再発を防いだ数百回の作業回に薄く分散されています。この体系はインシデントのたびに手強くなります。賢くなるのではありません。手強くなるのです。
フックは一つひとつが傷跡です。傷跡は一つひとつが教訓です。教訓は積み上がっていきます。2
よくある質問
フックの設定を見せてもらえますか
フック体系については エージェントセキュリティに関する NIST への意見書 で説明しており、AI エンジニアリングのシリーズ全体でも折に触れて言及しています。フックは ~/.claude/settings.json に登録され、~/.claude/hooks/dispatchers/ 経由でイベント種別ごとに振り分けられます。
フックはエージェントの性能にどう影響しますか
各フックはツール呼び出しごとに数ミリ秒を上乗せします。84個ある現状では、どのフックが発火するかにもよりますが、ツール呼び出しあたり合計200〜400ms です。モデルの推論時間(応答あたり2〜5秒)に比べれば無視できる水準で、ボトルネックはフックではありません。
フックは他の AI コーディングツールでも動きますか
フックは Claude Code 固有の仕組みです(PreToolUse、PostToolUse というイベントモデル)。考え方自体は、ミドルウェアやプラグインを備えたエージェントフレームワークなら何にでも当てはまります。個々の実装に移植性はありませんが、傷跡の分類体系と事後的に積み上げる方法論はどこでも通用します。
フックが行為を遮断すると何が起きますか
完全遮断(exit 2)は行為を止め、理由を説明するメッセージを差し込みます。エージェントは遮断理由を読んで方針を変えます。助言的なフック(exit 0)は懸念を記録しつつ行為を許可します。破壊的操作には完全遮断を、それ以外の大半のカテゴリーには助言的フックを使い分けています。合言葉の関門は、最も危険な操作(キャッシュパージ、インフラの削除)にのみ用います。
完全遮断と助言的の使い分けはどう決めていますか
完全遮断にするのは2種類です。破壊的操作(キャッシュパージ、データベース削除、強制プッシュ、インフラの変更)と、認証情報の露出(秘密ファイルの読み取り、鍵ストアへのアクセス)。それ以外はすべて助言的な記録に回します。分ける基準は結果の重さです。安く取り消せて秘密も漏らさないなら助言で十分。取り返しがつかないか認証情報を晒すなら、完全遮断が要ります。
出典
-
Blake Crosley, “What I Told NIST About AI Agent Security,” blakecrosley.com, 2026年2月。60回超の自律作業回における幻の検証率12%。Claude Code のライフサイクルイベント26種類のうち15種類を覆う84個のフック(v2.1.116)、逸脱検出の方法論。 ↩
-
Blake Crosley, “Compound Context: Why AI Projects Get Better the Longer You Stay With Them,” blakecrosley.com, 2026年3月。文脈が複利で効く枠組み。フックは、利得を積み上げる6つのカテゴリーの一つ。 ↩
-
Blake Crosley, “The Supply Chain Is the Attack Surface,” blakecrosley.com, 2026年3月。合成の隙間——個々には許可された部品が、許可されざる結末を生むこと。 ↩
-
Blake Crosley, “Deploy and Defend: The Agent Trust Paradox,” blakecrosley.com, 2026年3月。キャッシュパージ事故と、破壊的な API へのガードによる対応。 ↩
-
Christoph Riedl et al., “Agents of Chaos,” arXiv:2602.20021, 2026年2月。複数大学による14日間の研究(ノースイースタン、スタンフォード、ハーバード、MIT、カーネギーメロン)。6つの AI エージェント、過剰反応・なりすまし・無限ループを含む10件のセキュリティ脆弱性を特定。 ↩