AIDive

影片資料包

Claude Code Mods:fail-open 實驗、測量結果、檢查清單與來源

閱讀約 10 分鐘

TL;DR

  • Claude Code Mod 由三個檔案組成:.claude-plugin/plugin.json、內容為 {"modules":["./index.ts"]} 的 hooks/hooks.json,以及一個匯出 register(on) 的 TypeScript 模組。要載入它,只需要這些。
  • 在 2.1.272 版本上,/plugin-types 產生的型別檔 claude-code.d.ts 有 11,783 行,涵蓋 23 個名詞、共 84 個事件或呼叫名稱。fs.readFile 已經消失,現在是 fs.read 和 fs.write。
  • 一個 42 行的 tool.call hook 讓模型在 Read 和 Bash 兩個工具中讀到的都是 API_KEY=[REDACTED],而不是真正的金鑰。正常狀態下每次 hop 耗時 27.9 ms,對 session 增加的 token 約為 0。
  • 睡過 10 秒預算或丟出例外的 hook 會被跳過,下方的指令照常執行。日誌會記錄,但實際效果是被繞過:守衛型 Mod 是 fail open。
  • 生成路徑可行:一句話產出了 190 行的 Mod 加 53 行測試,約 4 分鐘、$1.23,claude plugin validate 零警告通過,claude plugin test 為 4 pass / 0 fail。
  • 有一件事沒能重現:Mod 在 claude -p 下可以載入,但在以 pty 驅動的互動式 REPL 中,兩次嘗試都毫無反應。在你的真實終端機上測過之前,請把 REPL 載入視為未驗證。

測量結果說明了什麼

以下全部在設定了 CLAUDE_CODE_ENABLE_FUNCTION_HOOKS 的 Claude Code 2.1.272 上執行,對象是一個手寫的 Mod redact-secrets,以及三個為了弄壞它而做的臨時 Mod。這個功能本身記錄在 Function Hooks 提案 issue 中,目前它仍是最接近官方規格的文件 s3。

工具比文件先到。2.1.272 版內建 claude plugin validate、test、eval 和 details;test 可以正常使用,即使 plugin --help 的指令區塊沒有列出它 s3。在 session 中執行 /plugin-types 會把 11,783 行寫進 .claude/types/claude-code.d.ts,另外還有一份 3,438 行的 claude-code-mcp.d.ts,涵蓋 7 個伺服器的 150 個 MCP 工具 s3。統計產生出來的型別,共有 84 個事件或呼叫名稱,分布在 23 個名詞上,而且你在九月文章裡讀到的一件事已經改名:fs.readFile 不再存在,介面是 fs.read 和 fs.write s9。同一份型別也說明,tool.call hook 回傳 { result, context? } 或 { deny };text 和 ref 來自核心,不屬於 hook 自己的回應,所以要改寫的是 result,不是 text s3。

claude plugin validate 會在 Mod 執行之前印出 footprint:./index.ts hooks: tool.call 和 ./index.ts calls: $.ui.toast,如果模組完全沒碰主機能力,則是 calls: nothing on $ s4。這行靜態輸出是 awesome-claude-code-mods 這類目錄今天唯一能自動化的審查,讀下一段時請記住這一點。

遮蔽在 -p 模式下有效。這個 42 行的 hook 攔截了 tool.call,檔案和 shell 輸出裡原本都是 sk-test1234567890abcdef,模型在 Read 工具和 Bash 工具中收到的卻都是 API_KEY=[REDACTED] s3。除錯日誌記錄到一次正常 hop 的往返為 27.9 ms,包含 worker hop 和 next() s3。claude plugin details 估算這個 Mod 為每個 session 增加約 0 個 token:Mod 是行程內的程式碼,不是提示詞文字,這也是推廣者用來對比 shell hook 和 skill 的主要論點 s7。

守衛是 fail open。一個睡 15 秒的 slow-guard Mod 在 10 秒預算處被切斷,日誌顯示 hook failed: slow-guard: exceeded 10000ms budget (tool.call; skipped; what is below it ran in its place),而 echo hi 照樣執行了 s3。一個會丟出例外的 throw-guard Mod 以同樣方式被跳過,hook failed: throw-guard: boom (tool.call; skipped; what is below it ran in its place),在 574.2 ms 時回報 s3。日誌裡很吵,效果上卻被繞過。任何以阻擋某事為職責的 Mod,都必須帶著這個前提來閱讀:守衛中的 bug 是一個漏洞,不是一次崩潰。

生成很便宜。從一句話開始,模型在約 4 分鐘、34 個 turn、$1.23 內寫出了 190 行可運作的 Mod 加 53 行測試(19,053 個輸出 token、497,282 個 cache read、8,357 個 thinking)s3。這個 Mod 通過 claude plugin validate 且沒有警告,claude plugin test 在 0.31 秒內 4 pass / 0 fail,並即時隱藏了金鑰 s3。對手寫的 Mod,claude plugin test 不需要 API 金鑰,也不呼叫模型:0.25 秒 1 pass s3。如果想用範本重複這個實驗,已經有一個教代理寫 Mod 的 skill s12。

沒能重現的部分:在以 pty 驅動的 REPL 中,Mod 兩次嘗試都沒有載入,所以 $.ui.toast 從未顯示;在 -p 下,等價的行為只以一行除錯訊息出現。我們觀察到的結果與 X 上流傳的「僅限 REPL」說法相反,但沒有找出根本原因 s8。5 秒的 worker 卡死 heartbeat 也沒有測試,只測了 10 秒 await 預算和 throw 路徑。

測量表

案例 Mod 做什麼 結果 時間
redact-secrets,Read 工具 在 tool.call 改寫 result 模型看到 API_KEY=[REDACTED] 每次 hop 27.9 ms
redact-secrets,Bash 工具 同一個 hook,shell 輸出 模型看到 API_KEY=[REDACTED] 每次 hop 27.9 ms
slow-guard 在 tool.call 內 sleep 15 秒 被跳過,echo hi 照常執行 在 10000 ms 被切斷
throw-guard 在 tool.call 內 throw boom 被跳過,指令照常執行 574.2 ms
生成的 Mod 一句話產出 190 行加 53 行測試 validate:無警告;test:4 pass / 0 fail 約 4 分鐘、34 turn、$1.23
對 redact-secrets 執行 claude plugin test 無 API 金鑰,無模型呼叫 1 pass 0.25 秒
claude plugin details Mod 的 session 成本 增加約 0 token n/a

測試條件:Claude Code 2.1.272,以環境變數啟用 function hooks。每個 Mod 都是一個外掛目錄,包含 plugin.json、hooks/hooks.json 和一個 index.ts。執行透過開啟除錯日誌的 claude -p 進行;預先放好的檔案和 shell 指令都含有 sk-test1234567890abcdef。fail-open 案例由一個睡 15 秒的 Mod 和一個丟出例外的 Mod 驅動,被守衛的指令是 echo hi。互動式 REPL 透過 pty 驅動,兩次嘗試都沒有載入 Mod。

週一就做

  • 在 session 中執行 /plugin-types,打開 .claude/types/claude-code.d.ts:在相信任何九月文章的程式碼片段之前,先搜尋 fs.read 和 tool.call。
  • 寫出三個檔案的 Mod 骨架(plugin.json、內容為 {"modules":["./index.ts"]} 的 hooks/hooks.json、匯出 register(on) 的 index.ts),並對它執行 claude plugin validate:讀一下 hooks: 和 calls: 的 footprint 行。
  • 把你最常用的 shell hook 移植成改寫 result 的 tool.call handler,然後把除錯日誌中的 hop 時間和 shell 版本做比較。
  • 在 Mod 旁邊加一個 claude plugin test 檔案,讓守衛在 CI 中不需要 API 金鑰就能執行。
  • 把每個守衛 handler 都包在 try/catch 裡,失敗時回傳 { deny }:在這個版本上,例外或 10 秒停滯會讓守衛被跳過,指令因而放行。
  • 分別在 claude -p 和你真實的互動式終端機中測試這個 Mod,並記下哪一個成功載入。
  • 安裝第三方 Mod 之前,先對它執行 claude plugin validate,凡是 calls: 行列出該 Mod 沒有理由使用的主機能力,一律拒絕。

延伸閱讀

  • 把提案 issue 從頭讀到尾,包括留言中附的架構 PDF:它是 register(on)、預算和 worker hop 唯一的書面約定 s3。
  • 與 Command Code Mods 的設計對照:它在主機行程中針對 ModApi 執行 TypeScript,兩個系統的形態和 early-access 限制相同 s2。
  • claudefa.st 的預覽寫於功能還只是提案的時候:可以看出 9 月 3 日的文字與 2.1.272 二進位檔之間改了什麼 s9。
  • Prathkum 的筆記推文是最清楚的簡短說明,解釋為什麼行程內 hook 在 token 和延遲上勝過 shell hook s7。
  • cc-mod-waitwhat 是很適合先讀的 Mod:UI 顯示在提示詞上方,不會寫入 transcript s11。
  • cc-arcade 展示了 $.ui 能做到多遠:從 Mod 在提示詞上方渲染遊戲 s5。
  • hooks 參考文件仍然描述 shell 模型;保持開啟,用來把每個舊事件對應到新的 noun.event 名稱 s1。
  • X 上的懷疑者認為外掛已經涵蓋這些需求,而且這個介面每次發布都會壞;改名後的 fs 名詞就是他們的一個論據 s23。

來源

  • Function Hooks proposal (issue #91870), GitHub, anthropics/claude-code. 為什麼值得讀:關於 Mod 唯一像規格的文字,留言中有架構 PDF 和上線過程。
  • Hooks reference, Claude Code docs. 為什麼值得讀:你正在遷移的 shell hook 模型,逐一事件說明。
  • Command Code Mods documentation, Command Code. 為什麼值得讀:同樣是 TypeScript 行程內的先例,有助於看出 Anthropic 沿用了什麼、避開了什麼。
  • awesome-claude-code-mods, GitHub, karanb192. 為什麼值得讀:自動掃描的公開 Mod 目錄,唯一的審查就是 validate footprint。
  • cc-arcade, GitHub, sezaakgun. 為什麼值得讀:讓這個功能被看見的示範,也是 $.ui 的導覽。
  • Boris Cherny announcement tweet, X, Boris Cherny. 為什麼值得讀:由 Anthropic 工程師發出的發布聲明,因為沒有部落格文章。
  • Prathkum: Function Hooks explained, X, Prathkum. 為什麼值得讀:對已經在寫 shell hook 的人來說,是 hook 與 Mod 差異最好的簡短說明。
  • shipnotesai reaction thread, X, shipnotesai. 為什麼值得讀:「僅限 REPL」說法流傳的地方,我們的實測結果與它相反。
  • Claude Code Function Hooks: Preview Behind a Flag, claudefa.st. 為什麼值得讀:上線前的說明,適合拿來對照提案與二進位檔的差異。
  • cc-mod-waitwhat, GitHub, GGGODLIN. 為什麼值得讀:一個小巧易讀的 Mod,寫入 UI 而不是 transcript。
  • claude-mods-skill, GitHub, BeLazy167. 為什麼值得讀:用來生成 Mod 的 skill,想重做「一句話」實驗時可以用。
  • AxialisSoftware reaction, X, AxialisSoftware. 為什麼值得讀:一則推文講完的懷疑論立場。

FAQ

現在 Mod 能取代我的 shell hook 嗎?

只要是必須阻擋的用途,還不能。在 2.1.272 上,丟出例外或停滯超過 10 秒的 hook 會被跳過,指令照常執行。Shell hook 仍然有效,所以負責阻擋的 hook 請留在 shell,等到有宣告式 catch 或 fail-closed 選項再說。

Mod 跑得好好的,為什麼 validate 還重要?

因為它的 hooks: 和 calls: 行是唯一能靜態看出 Mod 在 $ 上碰了什麼的方式。對你自己的 Mod,它能確認 footprint;對第三方 Mod,它就是程式碼在你的行程中執行之前,你能得到的全部審查。

一個 Mod 每個 session 的成本是多少?

claude plugin details 回報增加約 0 個 token。Mod 是在引擎中執行的程式碼,不是提示詞裡的文字,這是它相對於 skill 或 CLAUDE.md 規則的主要優勢。

為什麼 Mod 在 -p 下載入了,在 REPL 卻沒有?

不知道。兩次 pty 驅動的嘗試都沒有反應,而 claude -p 載入並套用了 hook。可能的原因是 pty 環境而不是功能本身,所以在依賴任何一個結論之前,先在你自己的終端機上測試。