如果我的需求本來就只是想在事後留下記錄或警告,而不是真的要阻止操作,那 PostToolUse 這種行為算不算是一個 bug?
嚴格來說不算功能性錯誤,比較接近誤導性的介面文字。回報者自己的說法也是如此——他不是在要求修正攔截機制本身,而是建議把顯示文字從「blocking error」改成更準確的「hook warning」或「hook reported error」。如果你的使用情境本來就是事後記錄、事後通知,那 PostToolUse 現在的行為完全符合你的需求,只是介面上的用字容易讓人誤判成「已經被擋下」。
既然連 PreToolUse 都有 issue #23284、#13744、#80039 這些失效案例,hook 是不是根本不能用來做真正的安全性把關?
這些案例確實提醒了一件事:hook 不該被當成唯一一道防線,尤其是牽涉到安全性的攔截需求。比較務實的做法是把 hook 視為多層防禦裡的其中一層,另外搭配像是沙盒層級的權限限制之類的機制一起部署,讓即使某一層失效,也還有其他層在運作。
更關鍵的習慣是:任何一條你寫的攔截規則,部署完之後都應該實際觸發一次去驗證,親眼確認目標操作真的沒有發生,而不是只看到終端機跳出錯誤訊息就當作驗證完畢。三個回報案例的共通點都是「exit 2 有跳出來,但操作還是執行了」——這種落差只有實測才驗證得出來。
exit code 跟 JSON 的 permissionDecision 兩種輸出方式,實務上該選哪一種?
官方文件給出的優先順序其實已經回答了這個問題的一部分:在可攔截的事件類型上,exit 2 的優先權高於同時存在的 JSON permissionDecision: "allow" 輸出。這代表 exit code 是更直接、也更難被覆蓋的訊號。
如果你的 hook 邏輯單純,只需要「允許」或「阻擋」兩種結果,裸的 exit code(exit 0 通過、exit 2 阻擋)通常已經足夠,也比較不容易出錯。JSON 輸出的價值在於能表達更細緻的資訊(例如附加提示訊息給 Claude),但如果你同時使用兩種輸出方式,務必記得 exit code 的優先權更高,debug 時要先確認 exit code 而不是只看 JSON 邏輯對不對。
我不是工程師,只是照網路上的範例複製貼上一段 hook 設定,該怎麼確認它有沒有真的生效?
三個檢查步驟,不需要看懂程式碼細節也能做:第一,打開設定檔確認事件名稱寫的是 PreToolUse 還是 PostToolUse——如果你的目的是「阻止」某個動作,範例卻寫成 PostToolUse,那從機制上就不可能達到阻止的效果。第二,找到攔截邏輯裡的 exit code 數字,確認它是 2 而不是 1——這是最常見的複製貼上失誤來源。第三,也是最重要的一步:故意觸發一次這條規則想擋下的情境,實際打開被操作的檔案或檢查指令執行結果,親眼確認它有沒有被真正影響到,不要只看終端機有沒有跳出錯誤字樣就當作驗證完畢。
你在 PostToolUse 事件寫了一個 hook,讓它在偵測到不該修改的檔案時回傳 exit 2。終端機上跳出一行紅字:「blocking error」。你鬆了一口氣,以為這次的修改已經被擋下來——結果打開檔案一看,內容還是被改了。這不是你的設定寫錯,而是 PostToolUse 這個事件類型的機制,從一開始就不具備「阻擋」的能力。
這個落差在社群裡已經被回報成一個明確的 GitHub issue(#19009):使用者的 PostToolUse hook 對某次 Edit 操作回傳 exit 2,介面上確實顯示了「blocking error」的字樣,但那次編輯早已經寫入磁碟。回報者的建議很直接——與其讓使用者誤以為出了問題就代表動作被擋下,不如把這段文字改成「hook warning」或「hook reported error」,更準確反映實際發生的事。
要理解為什麼會這樣,得先分清楚 PreToolUse 和 PostToolUse 在 執行時序上的根本差異。PreToolUse 在工具真正執行之前觸發,這個時間點上,Claude 還沒有對檔案系統或外部環境做出任何改動,所以 exit 2 在這裡是有意義的:它可以讓 Claude 收到指令,直接放棄這次操作。
PostToolUse 觸發的時間點完全不同——工具已經執行完畢,檔案已經被寫入,命令已經被送出,副作用已經發生。在這個時間點回傳 exit 2,能做到的只有把 stderr 內容回傳給 Claude、讓 Claude 知道「有些地方你可能做錯了」,但沒有任何機制可以讓已經寫入磁碟的內容自動復原。介面顯示「blocking error」,描述的其實是「這個訊息被標記為需要 Claude 注意的錯誤」,而不是「這個動作被攔截了」。
如果你的下一個念頭是「那我把所有攔截邏輯都搬到 PreToolUse 就好了」,這裡還有幾個必須知道的案例。Issue #23284 回報了一個 PreToolUse hook,設定用來比對 git push 指令的 Bash matcher,回傳 exit 2 之後,那次 push 依然被執行了。Issue #13744 則指出另一種落差:同樣是 PreToolUse、同樣回傳 exit 2,對 Bash 工具生效,但對 Write/Edit 工具卻沒有真正擋下操作。
更棘手的是 issue #80039,這一個案例限定在 Windows 平台:涉及巢狀引號解析的特定指令格式,會讓 exit 2 的處理和 stderr 的呈現同時失效,但同一個 session 裡其他 hook 卻能正常觸發。這意味著故障點更可能出在特定的指令解析路徑上,而不是整個 hook 機制在該平台上全面失靈——診斷時不能只看「這個平台的 hook 能不能用」,而要具體看「這種指令格式在這個平台上會不會觸發解析問題」。
在深入平台或事件類型的細節之前,值得先排除一個更常見、也更容易自己造成的問題。社群文章〈5 Claude Code Hook Mistakes That Break Your Automation〉(作者 yurukusa)記錄了一個高頻率出現的失誤:把原本該用 exit 2 的攔截邏輯寫成了 exit 1。exit 1 只會把錯誤內容記錄下來,不會真正產生任何攔截效果;如果你的 hook 明明想擋下某個操作,卻用了 exit 1,那麼無論事件類型選對還是選錯,攔截都不會發生。
官方文件(code.claude.com/docs/en/hooks)另外提供了一種結構化的替代方案:hook 可以回傳 JSON 格式的 hookSpecificOutput,其中包含 permissionDecision 欄位,用來表達「允許」或「拒絕」這次操作,而不只是依賴裸的 exit code。但文件同時明確說明了優先順序——在可攔截的事件類型上,exit 2 的優先權高於同時存在的 JSON permissionDecision: "allow" 輸出。也就是說,就算你的 JSON 邏輯判斷要放行,只要 exit code 回傳 2,Claude 仍然會把這次操作視為需要攔截處理。這個優先順序規則本身很直接,但也代表如果一套 hook 邏輯同時混用了兩種輸出方式,debug 時得先確認 exit code 到底回傳了什麼,而不是只檢查 JSON 內容對不對。
如果連 PreToolUse 都有已知的失效案例,那 hook 到底還能不能拿來做安全性把關?比較實際的答案是:可以,但不該把它當成唯一一道防線。攔截類的安全需求最好搭配其他機制一起部署(例如沙盒層級的權限限制),把 hook 視為其中一層,而不是全部。更重要的是,任何一條攔截規則寫完之後,都應該實際觸發一次,親眼確認被攔截的操作是否真的沒有發生——而不是只看終端機有沒有跳出錯誤訊息就當作已經驗證完畢。
複製別人的 hook 設定範例之前,先確認三件事:事件名稱是不是 PreToolUse 而不是 PostToolUse;攔截邏輯用的是不是 exit 2 而不是 exit 1;以及最關鍵的一步,實際觸發這條規則一次,去檢查目標檔案或指令有沒有真的被影響到。畫面上出現的錯誤訊息,只能告訴你「Claude 收到了一個需要注意的訊號」,不能告訴你「風險已經被排除」——這中間的落差,才是真正該去驗證的地方。