跳到主要內容
maxstudioNews
教學DEV Community (Claude Code)約 9 分鐘

Claude Code 鉤子比對器行為不一致:伺服器前綴在權限規則有效,卻無法觸發

這篇文章探討了 Claude Code 中鉤子比對器(hook matcher)的行為異常。研究發現,像 `mcp__lab` 這樣的伺服器前綴,雖然在權限規則中能有效匹配整個伺服器,卻無法觸發鉤子。這項測試揭示了文件與實際行為之間存在差異,並提供了正確配置鉤子比對器的指引,例如需使用 `mcp__lab__.*` 才能匹配伺服器內的所有工具。

Claude Code 鉤子比對器行為不一致:伺服器前綴在權限規則有效,卻無法觸發
圖片來源:DEV Community (Claude Code)

在 14 次無頭模式運行 Claude Code 2.1.289 的結果顯示,一個比對器設定為 mcp__lab 的鉤子,在對該伺服器工具的 42 次呼叫中,觸發次數為 0。然而,mcp__lab__.* 卻成功觸發了 42 次,且相同的 mcp__lab 字串若作為 --allowedTools 參數傳遞,則能批准所有相關呼叫。

另外兩個字串也保持靜默:if 條件為 mcp__lab(42 次呼叫中 0 次觸發),以及當相同伺服器被打包成外掛程式時,文件建議的修正方式 mcp__lab__.*(9 次呼叫中 0 次觸發)。MCP 伺服器上的鉤子通常用於記錄每次呼叫或在執行前進行檢查。

無論哪種情況,鉤子都位於比對器之後,而自然會輸入的內容是伺服器前綴,例如 mcp__github 或 mcp__memory,因為權限規則就是這樣命名整個伺服器的。然而,Claude Code 鉤子參考文件指出,該字串不起作用:「它會被精確字串比對,不匹配任何工具」。

一個不匹配任何東西的比對器不會造成任何可見的錯誤。設定檔會載入,會話會運行,但鉤子就是從未啟動。我們想了解這種情況發生的原因,以及還有哪些看似合理的字串會表現出同樣的行為,因此我們建立了一個包含三個工具的 MCP 伺服器,並同時註冊了 22 個鉤子群組,然後計算哪些群組針對哪些工具觸發。

以下所有測試均於 2026 年 10 月 5 日 UTC 時間 16:24 至 16:39 之間執行,使用 Claude Code 2.1.289(透過 claude --version 查詢)和 claude-opus-5-5 模型(透過 --model opus 選擇)。

文件引用來自 https://code.claude.com/docs/en/hooks 以及同一網站上的 hooks.md、permissions.md、mcp.md 和 cli-reference.md,加上 GitHub 上的 Claude Code CHANGELOG.md,所有文件均於同一天 UTC 時間 16:20 至 16:25 之間獲取。

關於比對器的文件說明:鉤子頁面根據比對器包含的字元進行分類。其表格有三行,引用如下:「*」、「""」或省略:「匹配所有」,「在事件的每次發生時觸發」。

「僅包含字母、數字、_、-、空格、, 和 |」:「精確字串,或由 | 或 , 分隔的精確字串列表,可選周圍的空白」。而「包含任何其他字元」則為「未錨定的 JavaScript 正規表達式」。

下一段補充說明,正規表達式比對器「使用 JavaScript 的 RegExp.prototype.test 進行測試,只要值中任何地方匹配就成功」,並且「當你需要完整字串匹配時」,應該將模式用 ^ 和 $ 包裹起來。對於工具事件(PreToolUse、PostToolUse 和其他三個),被測試的值是 tool_name。

MCP 部分很簡短。工具命名為 mcp__<server>__<tool>,然後:「要匹配伺服器中的每個工具,請在伺服器前綴後附加 .。. 是必需的:像 mcp__memory 或 mcp__brave-search 這樣的比對器只包含精確匹配字元,因此它會被精確字串比對,不匹配任何工具。」

對於外掛程式:「來自外掛程式捆綁的 MCP 伺服器的工具使用包含外掛程式名稱的範圍伺服器區段:mcp__plugin_<plugin-name>_<server-name>__<tool>。針對裸伺服器鍵(bare server key)編寫的比對器永遠不會觸發這些工具。」

另外兩個頁面將相同的字串用於其他用途。權限頁面在其 MCP 標題下寫道:「mcp__puppeteer 匹配由 puppeteer 伺服器提供的任何工具」,以及「mcp__puppeteer__* 使用萬用字元語法,也匹配 puppeteer 伺服器的所有工具」。因此,在權限規則中,裸伺服器前綴表示整個伺服器,而 * 是一個萬用字元(glob)。

鉤子頁面描述了每個處理器上的第二個過濾器,即 if 欄位,作為「權限規則語法,用於過濾此鉤子何時運行,例如 Bash(git ) 或 Edit(.ts)」。變更日誌中有兩條相關條目。2.1.191 版本:「修復了帶有逗號分隔比對器(例如 Bash,PowerShell)的鉤子默默地從未觸發的問題」。

2.1.195 版本:「修復了帶有連字號識別符(例如 code-reviewer、mcp__brave-search)的鉤子意外地進行子字串匹配的問題,它們現在進行精確匹配。使用 mcp__brave-search__.* 來匹配帶連字號的 MCP 伺服器中的所有工具。」

這為一個字串提供了三種語法。作為鉤子比對器,mcp__lab 是一個沒有任何工具擁有的精確名稱。作為權限規則,它是整個伺服器。作為 if 條件,文件將您指向權限規則語法,這再次暗示了整個伺服器。實驗室測試了所有這三種情況。

實驗室目錄沒有 git 儲存庫和 CLAUDE.md。它包含一個 MCP 伺服器、一個鉤子記錄器、一個設定檔和兩個小型 JSON 配置。該伺服器是一個沒有依賴項的 Node 腳本,它讀取標準輸入上的換行符分隔的 JSON-RPC,並回應 initialize、tools/list 和 tools/call。它將名稱作為其參數。

作為 lab 啟動時,它提供三個工具:ping、echo 和 echo_upper;作為 lab-two 啟動時,它只提供 ping。它收到的每個 tools/call 都會與伺服器名稱一起附加到 calls.log,因此我們有一個實際執行了哪些呼叫的記錄,這不依賴於 Claude Code。

--mcp-config 文件將其註冊兩次,分別為 lab 和 lab-two 伺服器,每個伺服器都指向 <lab>/server.js 腳本。這產生了四個工具名稱:mcp__lab__ping、mcp__lab__echo、mcp__lab__echo_upper 和 mcp__lab-two__ping。

第二個伺服器用於測試來自 2.1.195 條目的連字號,並且因為 lab-two 以 lab 開頭,所以要查看為 lab 編寫的鬆散模式是否也能捕捉到它。echo_upper 以 echo 開頭也是出於同樣的原因。

還有三個名稱來自外掛程式。一個名為 labkit 的目錄包含一個 .claude-plugin/plugin.json,將其命名為 labkit,以及一個 .mcp.json,它在鍵 lab 下註冊了相同的腳本,透過 --plugin-dir 載入。

其工具顯示為 mcp__plugin_labkit_lab__ping、__echo 和 __echo_upper,這正是文件描述的形式。

鉤子記錄器讀取鉤子的標準輸入,提取 tool_name 和 session_id,並將一行記錄附加到 fires.log,包含時間戳、會話 ID、事件名稱、標籤和工具名稱。.claude/settings.json 在 PreToolUse 和 PostToolUse 下各有 22 個比對器群組,每個群組都帶有一個運行記錄器並帶有其自身標籤的命令。

其中 19 個僅在比對器上有所不同。另外三個使用 mcp__.* 作為比對器並添加了一個 if 條件:mcp__lab、mcp__lab__* 和 mcp__lab__ping。比對器涵蓋了文件列出的內容以及我們預期人們會輸入的內容,包括裸前綴、文件建議的修正、精確工具、列表、萬用字元、正規表達式等。

每次運行都是一次 claude -p 呼叫,要求模型按順序呼叫七個工具各一次,並報告每個工具的 OK 或 FAILED。測試命令使用了 --setting-sources project,local、--strict-mcp-config、--mcp-config、--plugin-dir、--allowedTools 等參數來控制環境和權限。

--setting-sources project,local 保留了用戶設定,--include-hook-events 將 hook_started 事件放入流中,以便我們可以根據 Claude Code 自己的計數來檢查記錄器。

CLAUDE_CODE_DEBUG_LOG_LEVEL=verbose 使調試文件包含比對器查找。我們還取消設定了我們正在使用的會話導出的 CLAUDE* 變數,因此子運行不會繼承它們。

有三種條件:允許 (Allowed)(4 次運行),使用 --allowedTools 參數;無允許規則 (No allow rule)(7 次運行),沒有 --allowedTools;以及外掛程式 (Plugin)(3 次運行),移除了 --strict-mcp-config 並設定 ENABLE_CLAUDEAI_MCP_SERVERS=false。

每次運行都精確呼叫了它能看到的所有工具一次,在一次 ToolSearch 呼叫載入延遲的 MCP 工具之後。這使得總共有 65 次 MCP 工具呼叫到達 PreToolUse:42 次呼叫到 lab 伺服器的三個工具,14 次到 lab-two,以及 9 次到外掛程式的副本。

在所有 14 次運行中,記錄器寫入的行數等於流中 hook_started 事件的數量,總計 999 個,因此日誌是 Claude Code 自己的計數。

實驗結果顯示,每個比對器在每種條件下,要麼在每次呼叫工具時都觸發,要麼一次都沒有觸發,沒有部分觸發的情況。例如,mcp__lab__.、mcp__lab__、mcp__lab* 和 mcp__lab.* 都能成功匹配 lab 伺服器的工具,而 mcp__.* 則能匹配所有 lab、lab-two 和外掛程式的工具,以及內建的 ToolSearch。

有五個字串從未觸發鉤子:mcp__lab、mcp__lab-two、lab、ping 和 Bash|mcp__lab。這些字串都只包含精確匹配字元,因此它們被作為完整名稱進行比對,而沒有任何工具被精確命名為這些字串,這解釋了它們為何未能觸發鉤子。

Share

覺得值得一看?分享給朋友。

以上為 AI 自動翻譯導讀。原文版權歸 DEV Community (Claude Code) 所有, 建議透過「閱讀原文」前往原始網站,以取得最完整資訊並支持原作者。

Newsletter

不用每天追,AI 重點每週寄給你

43 個來源、每 2 小時更新,太多看不完。電子報每週挑最值得看的幾則,附上 Max 的看法。

每週一封。Email 只用來寄電子報,每封信都能一鍵退訂。