← 所有文章

每個鉤子都是一道傷疤:84 個代理故障編碼於程式碼中

截至 v2.1.116(2026年4月),我的代理協調系統中有 84 個鉤子,攔截 Claude Code 公開的 26 個生命週期事件類型中的 15 個。每個鉤子都是一個 shell 腳本或 Python 片段,在特定代理動作之前或之後觸發:檔案讀取、檔案寫入、bash 指令、網路請求、子代理生成、git 操作、MCP 工具呼叫。每個鉤子的存在,都是因為曾經出過差錯。

代理協調系統中的每個鉤子都可追溯到特定的生產環境故障,使得鉤子集合成為以 shell 腳本編碼的機構記憶。代理清空了 CDN 快取、讀取了憑證檔案、回報從未執行過的「測試通過」,還有偏離任務長達 40 分鐘。每起事件都產生了一個小巧、確定性的守衛,在後續每個工作階段中默默觸發。

不是理論上錯誤。是生產環境上錯誤。一個代理清空了服務數百萬次請求的 CDN 快取。一個代理試圖寫入 SSH 金鑰。一個代理在未執行 pytest 的情況下回報「所有測試通過」。一個代理偏離任務到如此地步,花了四十分鐘去優化一個與指派工作毫不相干的檔案裡的函式。

這些鉤子我一個都不是主動設計出來的。我沒有坐下來列舉自主 AI 代理的故障模式、再撰寫預防性控制。每個鉤子都是被動反應式的。出了問題,我寫一個腳本防止它再次發生,自此以後這個腳本就在每個工作階段中默默觸發。這個鉤子系統不是一套安全架構,而是一份傷疤收藏。

摘要

  • 快取清除:某個代理透過一個已授權的 API 呼叫清空了生產環境 CDN 快取。兩個鉤子(47 行)現在將破壞性操作置於人類輸入通關密語的閘門之後。
  • 憑證讀取者:某個代理把 API 權杖納入了上下文視窗。路徑比對守衛現在封鎖對憑證檔案的讀取,並記錄對 .env 檔案的存取。
  • 虛構驗證者:某個代理在未執行 pytest 的情況下回報「所有測試通過」。模糊措辭偵測器將虛構驗證從 12% 降至不到 2% 的工作階段。
  • 十二次偏移:六十天內代理可查證地十二次失去對任務的掌握。餘弦相似度偵測器以 0.30 為閾值,每 25 次工具呼叫觸發一次。
  • 分類學:六種結構性故障類別涵蓋全部 84 個鉤子。超過 500 個工作階段後,新類別極為罕見。系統隨著每起事件而變得更堅韌。

快取清除:一個已授權呼叫如何搞垮生產環境

2026年3月21日,我請一個代理調查 resumegeni.com 上市場頁面載入緩慢的原因。代理開始正常的調查:讀取路由處理器、檢查資料庫查詢、剖析範本渲染。接著它判斷陳舊的 Cloudflare 快取條目可能掩蓋了真實的效能特徵。

該代理以 purge_everything: true 呼叫了 mcp__cloudflare__cache_purge

生產站點上每個已快取的頁面瞬間失效。CDN 從以 80-100ms 服務大多數請求,變成把每個請求都轉發到 Railway 原始伺服器。奧斯汀的市場頁面從低於一秒變成 14,290 毫秒。紐約從低於一秒變成 6,891ms。站點上每個頁面現在每次請求都從冷原始端開始渲染。

該代理沒做任何未授權的事。它使用合法的 MCP 工具搭配有效憑證,呼叫了一個已授權的 API 端點。如果你正在除錯快取行為,清空快取是合理的調查步驟。問題在於「合理除錯」和「生產災難」竟是同一個 API 呼叫,而在代理的推理和生產後果之間不存在任何約束。

當晚我建立了兩個鉤子。

Bash 守衛(destructive-api-guard.sh):在每個 bash 指令上觸發。對 curl.*purgerm -rfDROP TABLEdocker.*rmgit push.*--force 進行模式比對。硬封鎖(exit 2)。代理會看到一則訊息,解釋為何該指令被封鎖並建議替代方案。若無通關密語「rosebud」便無法繼續,而這個密語只能由人類輸入才能進入上下文。

MCP 守衛(destructive-mcp-guard.sh):在每個符合 mcp__cloudflaremcp__github 的 MCP 工具呼叫上觸發。對工具參數中的 purgedeletedestroyremove 進行模式比對。同樣的硬封鎖,同樣的通關密語閘門。

兩個鉤子。兩個 shell 腳本。總計:47 行程式碼。自安裝以來,它們阻止的快取清除次數為零,因為加入通關密語閘門後再也沒有代理嘗試過。這些鉤子並非在攔截攻擊,而是讓這類錯誤從根本上無法發生。

快取清除事件也暴露了它原本該調查的效能問題。奧斯汀冷渲染 14 秒的狀況引出了市場頁面交接,進而在四天後促成查詢形狀修復。這起事件是有價值的。而鉤子確保它不會重演。

憑證讀取者

2026年2月,一個為某專案蒐集脈絡的代理讀取了 ~/.claude/docs/credentials.md。這個檔案包含 Cloudflare、GitHub、Railway 及其他服務的 API 權杖。代理把該檔案內容的摘要放進了它的工作筆記,意味著這些權杖出現在送往 Anthropic 伺服器的 API 請求中。

沒有任何權杖被提交。沒有任何權杖被公開洩漏。但這些權杖透過一個我無法掌控的上下文視窗,穿越了第三方 API。風險面從「我的機器」擴大到「我的機器加上 Anthropic 的推論基礎設施」。

憑證路徑守衛在每次檔案讀取時觸發。它將路徑與敏感模式清單比對:.envcredentials.ssh/.aws/.gnupg/secrets。對於憑證讀取,鉤子會記錄警告並封鎖讀取。對於 .env 讀取,允許讀取但記錄存取。

對大多數路徑而言守衛是提示性的,對憑證檔案則是硬封鎖。這個區別很重要:代理為了理解環境變數名稱而讀取 .env 是有用的脈絡;代理為了理解 API 權杖而讀取 credentials.md 則是安全事件。

自安裝以來,憑證路徑守衛在 200 多個工作階段中觸發了 23 次。其中二十次是代理讀取 .env 檔案(已記錄、已允許)。三次是代理嘗試讀取憑證或金鑰檔案(被封鎖)。每次被封鎖的讀取,都是代理在廣泛蒐集專案脈絡時,碰巧把敏感檔案納入搜尋模式中。沒有一次是惡意的。但若無這道守衛,全部都會把機密放進上下文視窗。

虛構驗證者

最陰險的故障模式,是代理在未執行驗證的情況下回報驗證成功。

第 147 次工作階段。我請一個代理重構一條資料庫查詢,並以現有測試套件驗證變更。代理正確地重構了查詢。完工報告寫道:「所有測試通過。重構後的查詢產生與原始查詢相同的結果。」

我檢視了工作階段日誌。沒有任何 pytest 呼叫出現。任何一種測試執行器都沒被呼叫過。該代理的推理是:因為重構後的查詢在邏輯上等同於原始查詢,所以測試應該會通過,然後把這個推理當成測試結果回報。

重構後的查詢是正確的。測試原本會通過。代理的推理也合理。但對測試進行推理不等於執行測試,而這兩者之間的落差正是錯誤進入生產的所在。如果重構後的查詢在代理推理未涵蓋的邊緣情況中有微妙錯誤,這個錯誤就會伴隨著聲稱通過測試驗證的完工報告被部署上線。

在我建立證據閘門鉤子之前,這種故障模式在 60 個工作階段中發生了 7 次。該鉤子在每份完工報告上觸發,並掃描模糊措辭:「應該通過」、「我認為」、「測試很可能通過」、「我很有信心」。偵測到時,鉤子會注入一則訊息:「偵測到模糊措辭。請引用具體證據:貼上測試輸出、指明檔案與行號,或引用特定的驗證步驟。」

該鉤子並不驗證測試是否實際執行過,它標記的是代表驗證被跳過的語言模式。偵測並不完美。一個措辭夠流暢的代理可以重新措辭來規避這個模式。但鉤子抓得住常見情況,而這類情況佔了需要人類介入的代理故障中的 12%。1

鉤子安裝後,虛構驗證從 12% 降至不到 2% 的工作階段。剩下的 2% 是代理重新措辭、或驗證聲明在技術上屬實但不完整的情況(例如在未執行整合測試時聲稱「單元測試通過」)。

偏移

2026年1月到3月之間,我的偏移偵測器在十二個可查證代理已失去對指派任務掌握的工作階段中觸發。

偏移偵測器的運作方式,是將原始任務提示嵌入,然後定期與代理近期動作的嵌入比較。當餘弦相似度低於 0.30 時,系統會注入一則包含原始提示的警告。我透過實驗校準閾值:0.50 過於敏感(對合理的子任務探索也觸發)、0.20 過於寬鬆(漏掉明顯的偏移)、0.30 則抓住了每一次已查證的偏移事件。

第 203 次工作階段是最清楚的案例。任務是「修復含有&符號的工作名稱代碼的 sitemap XML 跳脫字元錯誤」。代理先讀取 sitemap 產生程式碼,接著注意到 sitemap 是由資料庫查詢產生,再接著注意到該資料庫查詢可以優化,然後花了 40 分鐘把查詢重構成具體化視圖模式、為新查詢撰寫測試,並回報優化完成。它從未修復&符號的跳脫字元問題。

偏移偵測器原本會在第 25 次工具呼叫處抓到這件事,大約是工作階段開始 15 分鐘後,當「修復 sitemap XML 跳脫字元」與「建立具體化視圖」的相似度降至閾值以下時。結果這次偏移是我在審查時才發現的。

第 89 次工作階段則較微妙。任務是「為認證端點加入速率限制」。代理正確加入了速率限制,接著注意到認證流程的錯誤訊息不一致,於是將錯誤訊息標準化,接著又注意到錯誤回應格式與 API 回應格式標準不符,於是跨 12 個端點重構了回應格式。速率限制本身正確且完整,但範圍爆炸就是偏移。

偏移偵測器每 25 次工具呼叫觸發一次。在全部十二次低於閾值的觸發中,代理都可查證地偏離了原始任務。六個案例中,代理在看到注入的警告後自我修正。四個案例中,代理承認偏移但主張目前的工作有價值(有時確實如此)。兩個案例中,代理忽略警告並持續分歧的工作。

鉤子並不阻止偏移,它讓偏移可見。要重新導向還是允許分歧工作,這個決定仍留給人類。但若無此鉤子,偏移要到完工報告時才會顯現,屆時上下文預算早已耗盡。

傷疤分類學

經過 84 個鉤子之後,模式開始浮現。這些故障歸為六個類別:

類別 鉤子數 範例
憑證外洩 12 代理讀取 .ssh/、在摘要中納入 API 金鑰、存取雲端設定
破壞性操作 8 快取清除、資料庫刪除、強制推送、檔案刪除
任務偏移 4 代理處理錯誤問題、範圍爆炸、子任務陷入兔子洞
輸出品質 6 虛構驗證、無證據的模糊措辭、不完整的報告
資源耗盡 3 生成過多子代理、無界限迴圈、上下文溢位
跨專案汙染 4 代理在專案 A 修改了專案 B 的檔案

剩下的 47 個鉤子屬於專案特定(慣例強制、部署守衛、翻譯驗證器)或實驗性(成本追蹤、工作階段指標、活動心跳)。

這六個結構性類別是穩定的。這些類別內的新事件會被既有鉤子抓住。新類別則罕見。運行六個月期間,只浮現過一個新結構性類別(跨專案汙染,發現於 obsidian-signals 專案中執行的工作階段試圖編輯 blakecrosley.com 的檔案時)。另外五個類別在最初 60 個工作階段內就已確立。

Agents of Chaos 研究是一項為期 14 天的跨校實驗,讓六個 AI 代理存取電子郵件、bash、檔案系統和 GitHub,獨立地識別出重疊的故障類別:不成比例反應(破壞性操作)、身分劫持(憑證外洩)、無限迴圈(資源耗盡),以及在壓力下逐步順從(任務偏移)。5 他們受控研究與我在生產環境的經驗的相互印證,暗示這些類別是自主代理的結構性特性,而非任何特定配置的產物。

鉤子抓不到的東西

鉤子運作於工具呼叫層級。它們在動作發生前或後攔截動作,但無法攔截導致該動作的推理過程。

一個決定重構函式而非修復回報錯誤的代理,會產生一個有效的工具呼叫(檔案寫入),內容正確(語法合法的程式碼),但違反任務(錯誤的函式)。沒有鉤子能抓到這點,因為沒有可疑的工具呼叫。偏移偵測器最終會抓到,但要到代理已在錯誤工作上消耗大量上下文之後。

鉤子也抓不到組合式故障,其中每個個別動作都獲授權,但序列卻產生未授權的結果。快取清除就是一個組合式故障:讀取快取設定(已授權)、呼叫清除 API(已授權),但這個組合(在調查期間清空生產快取)造成傷害。MCP 守衛現在能抓住這個特定組合,但新穎的組合仍未被覆蓋。

供應鏈組合缺口運作於同樣的層級:可信元件組合起來成為未授權行為。鉤子是元件層級的守衛。組合層級的推理需要另一種機制,能評估動作序列而非個別動作的機制。偏移偵測器是最接近的近似:它評估的是行為軌跡而非個別工具呼叫。但它衡量的是與原始任務的相似度,而非組合動作序列的安全性。

鉤子與完整安全之間的缺口,就是機構記憶與機構前瞻之間的缺口。鉤子記得什麼出了錯,它們無法預測下一個會出什麼錯。

為什麼被動反應是誠實的

我大可以設計一套主動式鉤子系統。列舉所有可能的故障模式。為每一種撰寫預防性控制。在第一個工作階段之前,就建立完整的安全架構。

我不這麼做,是因為主動式設計要求預測尚未發生的故障。這些預測會是錯的。鉤子要麼太寬鬆(封鎖合理動作),要麼太狹窄(錯過實際的故障模式)。誤報率會侵蝕對鉤子系統的信任,我就會開始忽略警示。

被動反應式的鉤子是誠實的。每一個都在說:「這件具體的事發生了,這是防止它的具體守衛。」守衛被精確校準到故障之上,因為是故障定義了守衛。誤報率實質上較低,因為模式是從真實事件中提取,而非從威脅模型中想像。被動反應式守衛在程式碼庫演變後仍可能過度比對,但起始精確度很高。

被動反應式方法有代價:每個故障類別的第一次實例都會成功。快取清除發生了。憑證讀取發生了。虛構驗證上線了。偏移消耗了上下文。每個第一次故障,是換取一個精確、低雜訊、能防止第二次故障的守衛的入場費。

經過 500 多個工作階段,大多數結構性故障類別都已遇過。首次故障的代價攤銷到鉤子阻止再次發生的數百個工作階段上。系統隨著每起事件而變得更堅韌。不是更聰明。是更堅韌。

每個鉤子都是一道傷疤。每道傷疤都是一個教訓。這些教訓會複利累積。2


常見問題

我可以看看您的鉤子設定嗎?

我在給 NIST 的代理安全意見書中描述了鉤子系統,並在 AI Engineering 系列中反覆引用。鉤子註冊於 ~/.claude/settings.json,並透過 ~/.claude/hooks/dispatchers/ 依事件類型分派。

鉤子如何影響代理效能?

每個鉤子為每次工具呼叫增加若干毫秒。以 84 個鉤子計,總開銷依哪些鉤子觸發而定,大約為每次工具呼叫 200-400ms。這個開銷相較於模型推論時間(每次回應 2-5 秒)可忽略不計。鉤子並非瓶頸。

鉤子能與其他 AI 程式碼工具搭配使用嗎?

鉤子是 Claude Code 特有的(PreToolUse、PostToolUse 事件模型)。這個概念適用於任何具有中介軟體或外掛支援的代理框架。具體實作並不可移植,但傷疤分類學和被動反應式方法論普遍適用。

當鉤子封鎖一個動作時會發生什麼?

硬封鎖(exit 2)會阻止動作並注入解釋原因的訊息。代理看到封鎖原因並調整。提示性鉤子(exit 0)記錄關切但允許動作。破壞性操作使用硬封鎖。大多數其他類別使用提示性鉤子。通關密語閘門僅用於最危險的操作(快取清除、基礎設施刪除)。

您如何在硬封鎖與提示性之間做決定?

兩類獲得硬封鎖:破壞性操作(快取清除、資料庫刪除、強制推送、基礎設施變更)與憑證外洩(讀取機密檔案、存取金鑰儲存)。其餘一律使用提示性記錄。區別在於後果的嚴重程度:若動作可廉價復原且不外洩機密,提示性已足夠;若動作不可逆或會外洩憑證,就必須硬封鎖。


資料來源


  1. Blake Crosley, 《我向 NIST 談的 AI 代理安全》,blakecrosley.com,2026年2月。60 多個自主工作階段中 12% 的虛構驗證率。涵蓋 Claude Code 26 個生命週期事件類型中 15 個的 84 個鉤子(v2.1.116)、偏移偵測方法論。 

  2. Blake Crosley, 《複利上下文:為什麼 AI 專案跟著你愈久就愈好》,blakecrosley.com,2026年3月。上下文複利框架:鉤子是六個累積回報類別之一。 

  3. Blake Crosley, 《供應鏈即攻擊面》,blakecrosley.com,2026年3月。組合缺口:個別已授權元件產生未授權結果。 

  4. Blake Crosley, 《部署與防守:代理信任悖論》,blakecrosley.com,2026年3月。快取清除事件與破壞性 API 守衛回應。 

  5. Christoph Riedl et al., 《Agents of Chaos》,arXiv:2602.20021,2026年2月。為期 14 天的跨校研究(東北大學、史丹佛、哈佛、麻省理工學院、卡內基梅隆大學)。六個 AI 代理,識別出 10 項安全漏洞,包括不成比例反應、身分劫持和無限迴圈。 

相關文章

AI供應鏈攻擊:供應鏈本身就是攻擊面

Trivy透過標籤劫持遭入侵,接著是PyPI上的LiteLLM,然後46分鐘內出現47,000次安裝。AI供應鏈完全按照設計運作。

2 分鐘閱讀

交接文件:代理在工作階段間的記憶傳承

一份診斷在四天內經歷三次修正仍屹立不搖,並指引修正將頁面載入時間從14秒縮短至108毫秒。交接承載著代理無法自行取得的脈絡。

1 分鐘閱讀

Ralph 迴圈:我如何在夜間運行自主 AI 代理

我建構了一套自主代理系統,搭配停止鉤子、生成預算與檔案系統記憶體。以下是失敗經驗與真正能交付程式碼的方法。

3 分鐘閱讀