我已經有一個會回 exit 2 的 PreToolUse hook,還需要 onFailure 嗎?
需要考慮。exit 2 只在腳本正常跑起來、而且主動回 2 的時候才生效。腳本路徑打錯、執行檔沒權限、逾時,或腳本中途當掉以 1 結束,這些情況守門員都沒有「做出判決」,預設就是放行。onFailure: "block" 補的正是「守門員沒上班」的情況,兩者是互補的。
怎麼驗證我的 hook 真的 fail-closed?
在一個用完即丟的目錄裡,用 claude -p 小範圍測。分別做三件事:把腳本路徑改錯、在腳本裡加 sleep 讓它超過 timeout、讓腳本直接 exit 1。每一種都請 Claude 跑一個本來會被擋的無害指令,確認它被擋下,再還原設定確認安全指令仍然能通過。Developers Digest 的測試就是用這個思路,且只改 hook 這一個變數。
prompt hook 和 agent hook 也能用 onFailure 嗎?
依 Developers Digest 的整理,目前不涵蓋:onFailure 只適用於 command 與 HTTP hook。需要硬性攔截的規則,建議用 command hook 當守門員,並按前面的寫法讓它 fail-closed;prompt 與 agent hook 比較適合做需要語意判斷的軟性檢查。v2.1.294 也提到,寫成指示語句的這兩類 hook 先前有放行本該阻擋動作的問題,可見它們不適合當唯一的防線。
我不是工程師,只負責團隊的規範,這件事跟我有什麼關係?
如果團隊靠 hook 來強制規範(例如禁止刪除檔案、禁止直接推到 main),你可以請工程師回答一個簡單的問題:「守門的腳本壞掉或逾時的時候,會發生什麼?」預設答案是放行,而且畫面上只有一行不起眼的錯誤。要確認有沒有人為關鍵規則設定 onFailure: "block",並且有人定期驗證這道閘門還在運作。
很多人把 hook 當成「保證會擋」的安全閘門,但官方 hooks 文件寫得很清楚:只有 exit code 2 能單靠退出碼阻擋動作。其他非零退出碼不會阻擋,只會在 transcript 顯示一行 hook error。這代表守門的腳本一旦壞掉,預設結果是放行。2026 年 10 月 8 日的 v2.1.295 新增了 onFailure: "block",專門補這個洞。
根據官方 hooks 文件,以下情況對 PreToolUse 都不會阻擋:
更新說明的描述是:command 與 HTTP hook 可以設定成,當 hook 無法啟動、逾時或以非預期的退出碼結束時,改為阻擋動作,而不是放行。Developers Digest 在 10 月 9 日發表的實測用 v2.1.295 的 claude -p 跑了六組設定,只改 PreToolUse hook:沒設定時,腳本不存在和逾時兩種情況都讓指令照樣執行;設成 "onFailure": "block" 後兩者都被擋下,而正常運作的守門腳本仍然放行安全指令。
該文也指出範圍:它只對可以阻擋的事件有意義,例如 PreToolUse、UserPromptSubmit、Stop;prompt、agent 與 MCP tool hook 不在涵蓋範圍內。以下的設定寫法來自該文的範例(我讀到的官方 hooks 文件頁面沒有出現這個鍵,採用前請用官方文件或 claude Plugin validate 再確認):
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/guard.sh",
"timeout": 5,
"onFailure": "block"
}
]
}
]
}
}
只加 onFailure 不夠。如果腳本是「跑完了,但因為某個指令失敗而以 0 結束」,Claude Code 看到的就是一次成功的放行。Developers Digest 的作者發現,一個沒做錯誤處理的守門腳本,在沒有安裝 jq 的機器上會以 0 結束,等於讓 rm -rf 通過。比較穩的寫法是讓任何非預期的錯誤都以 2 結束。下面是依這個原則寫的示意(比對規則很粗糙,只是為了示範流程):
#!/usr/bin/env bash
set -euo pipefail
trap 'echo "guard crashed" >&2; exit 2' ERR
cmd=$(jq -r '.tool_input.command')
if printf '%s' "$cmd" | grep -Eq 'rm +-rf +/'; then
echo "blocked: destructive rm" >&2
exit 2
fi
這裡的 trap ... ERR 讓 jq 不存在這類失敗直接變成 exit 2;而 if 條件裡的 grep 沒比對到時回傳 1,不會觸發 trap。
fail-closed 有代價:如果守門用的 HTTP 端點掛了,所有 Bash 呼叫都會被擋;格式化或通知這類方便性 hook 壞掉,也沒有必要卡住整個 session。以下是我的建議,屬於推論,不是官方規範:
onFailure: "block"。v2.1.294(10 月 8 日)修正了寫成指示語句的 prompt 與 agent hook(例如「Block commands that…」)會放行本該阻擋的動作;Stop 與 SubagentStop 上的 prompt hook 判斷也改善,Claude 比較不容易提早停下。更早的 v2.1.288 更新說明摘要也提到,PreToolUse hook 因比對失敗而被略過的情形,現在改為阻擋該次呼叫(細節請看原始更新說明)。同一個月內連續出現好幾個「守門沒守住」的修正,值得把自己的 hook 重新驗一次。