博客 • Claude Code / Agent SDK
升級到 Claude Code 2.1.317 之後,Agent SDK hook 設置 onFailure 爲 block 時,回調失敗要在 JSON 日誌裏怎樣記下攔截?
2026 年 11 月 3 日,Anthropic 發佈 Claude Code 2.1.317。Agent SDK hook 回調配置新增 onFailure 取值 block:當 PreToolUse 等 hook 腳本非零退出或超時時,客戶端不再 fail-open 繼續執行 tool,而是攔截該次 tool 動作並在審計 JSON 裏記下 blocked。
hook 回調結果 JSON 應寫出 hookName、onFailure block、exitCode 與 timedOut;tool 調用被攔後的審計 JSON 應寫 decision blocked、reason hook_callback_failed,並保留 hookName 與 exitCode。運維可用本地 hook_failure_gate 對照 schema、not_block_mode、allow、block、timeout_block 五條分支,而不是把回調失敗當成 permission policy deny。
allow 與 deny 隊列寫法見 Agent permission policy;Managed Agents 的 auto 評測上下文見 Managed Agents auto 評測;會話級工作流 JSON 見 Claude Code workflow。本篇只寫 2.1.317 的 onFailure block、hooks settings 與回調結果 JSON、Bash 調用樣本、hook_callback_failed 審計字段,以及 hook_failure_gate 怎樣分支。
- hooks settings 裏 PreToolUse 等條目必須聲明 onFailure 爲 block 纔會在回調失敗時攔截 tool;未聲明或取值不是 block 時 hook_failure_gate 應返回 not_block_mode,不要寫 reason hook_callback_failed。
- hook 回調結果 JSON 必須帶 hookName、onFailure、整數 exitCode 與布爾 timedOut;exitCode 爲 0 且 timedOut 爲 false 時返回 allow,非零 exitCode 且 onFailure block 時返回 block。
- 審計日誌在 block 分支應寫 decision blocked、reason hook_callback_failed,並保留 hookName 與 exitCode;SIEM 應按 hookName 聚合而不是混用 permission policy 的 deny reason。
- timedOut 爲 true 且 onFailure block 時 hook_failure_gate 返回 timeout_block;settings 或回調結果 JSON 形狀錯誤時先返回 schema,再進入 block 判定。
先記住這一點:2.1.317 改的是 hook 回調失敗時的 block 語義與 hook_callback_failed 審計字段,不是重寫 permission policy 裏的 allow 或 deny 表。下面按 changelog、hooks settings JSON、回調結果 JSON、Bash 調用樣本、審計日誌與對照表、hook_failure_gate 源碼,以及 JSONNote 裏怎樣校驗形狀。
2.1.317 怎樣讓 hook 回調失敗時 block 而不是 fail-open?
變更說明見 Claude Code changelog,標籤頁見 v2.1.317 release。相對更早 Agent SDK hook 行爲,最顯眼的是 onFailure block:PreToolUse 等 hook 腳本非零退出或超時時,tool 動作被攔截,而不是記錄警告後繼續執行。
攔截髮生在 hook 回調完成之後、tool 執行之前。審計 JSON 應保留 decision blocked、reason hook_callback_failed、hookName 與 exitCode,方便 SIEM 按失敗 hook 歸因,而不是隻記一條泛化的 tool denied。
PreToolUse 與 onFailure block 在 hooks settings JSON 裏怎樣寫?
在用戶或項目 settings 的 hooks 對象裏爲 PreToolUse 配置 matcher 與 command,並在同一條目設置 onFailure 爲 block。2.1.317 起只有顯式 block 纔會在回調失敗時攔截 tool;缺省或其它取值仍走 fail-open 或 warn 路徑,hook_failure_gate 應返回 not_block_mode。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "./hooks/pre-bash.sh",
"onFailure": "block"
}
]
}
]
}
}
若運維把 onFailure 寫成 Block 大小寫混用或漏掉 hooks 層級,本地 schema 校驗應先失敗並返回 schema,再進入 hook_failure_gate 的 block 分支。不要把形狀錯誤當成 not_block_mode。
hook 回調結果 JSON 裏 hookName 與 exitCode 怎樣寫?
客戶端在 hook 回調完成後寫入結果 JSON:hookName 標識觸發的 PreToolUse 條目,onFailure 回顯 settings 裏的 block,exitCode 爲腳本進程退出碼,timedOut 表示是否因超時被殺。permission 仍 allow 的 Bash 調用也可能因 hook 失敗而被 block。
{
"hookName": "pre-bash.sh",
"onFailure": "block",
"exitCode": 2,
"timedOut": false
}
exitCode 必須爲整數,timedOut 必須爲布爾;缺 hookName 或 onFailure 形狀錯誤時 hook_failure_gate 先返回 schema。onFailure 不是 block 時返回 not_block_mode,避免誤用 hook_callback_failed 日誌模板。
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"additionalProperties": false,
"required": ["hookName", "onFailure", "exitCode", "timedOut"],
"properties": {
"hookName": { "type": "string", "minLength": 1 },
"onFailure": { "type": "string", "enum": ["block", "continue"] },
"exitCode": { "type": "integer" },
"timedOut": { "type": "boolean" }
}
}
Bash tool 調用 JSON 在 hook 攔截前後應怎樣對照?
樣本:permission policy 已對 Bash allow,tool 調用 JSON 含 tool Bash 與 command npm test。PreToolUse hook 腳本 exitCode 2 且 onFailure block 時客戶端不執行 Bash,審計 JSON 寫 decision blocked 與 reason hook_callback_failed。對照時應保留原始 command 與 hookName,便於 hook_failure_gate 走 block 分支。
{
"tool": "Bash",
"command": "npm test"
}
{
"hookName": "pre-bash.sh",
"onFailure": "continue",
"exitCode": 2,
"timedOut": false
}
審計日誌裏 decision blocked 與 hook_callback_failed 應怎樣寫?
hook 回調失敗且 onFailure block 時,tool 審計 JSON 應寫 decision blocked、reason hook_callback_failed,並保留 hookName 與非零 exitCode;timedOut 爲 true 時可額外標記 timeout 字段但 reason 仍用 hook_callback_failed。不要把 permission deny 與 hook block 混在同一 reason 枚舉裏。
{
"tool": "Bash",
"decision": "blocked",
"reason": "hook_callback_failed",
"hookName": "pre-bash.sh",
"onFailure": "block",
"exitCode": 2
}
| 日誌或調用現象 | hook_failure_gate 返回 | 運維動作 |
|---|---|---|
| exitCode 0,timedOut false,onFailure block | allow | tool 可執行;日誌無需 hook_callback_failed。 |
| exitCode 非零,onFailure block,timedOut false | block | 對齊 decision blocked 與 reason hook_callback_failed 及 exitCode。 |
| onFailure 未設 block 或取值 warn | not_block_mode | 不要寫 hook_callback_failed;對照 fail-open 舊報表。 |
| timedOut true 且 onFailure block | timeout_block | 日誌仍用 hook_callback_failed;SIEM 單獨計數 timeout_block。 |
hook_failure_gate 怎樣在 schema 與五條業務分支間選擇?
實現順序:先用 hooks settings 與回調結果 JSON Schema 校驗 onFailure 與 hookName、exitCode、timedOut;onFailure 不是 block 返回 not_block_mode;timedOut true 且 block 返回 timeout_block;exitCode 0 返回 allow;非零 exitCode 且 block 返回 block;形狀錯誤返回 schema。
不要把 block 與 timeout_block 合成單一 hook_failed 位。報表分開計數,才能區分 not_block_mode fail-open、exitCode 失敗 block、以及真正的 timeout_block。
def actual_type(data: object) -> str:
if data is None:
return "null"
if isinstance(data, bool):
return "boolean"
if isinstance(data, list):
return "array"
if isinstance(data, int):
return "integer"
if isinstance(data, float):
return "number"
if isinstance(data, str):
return "string"
if isinstance(data, dict):
return "object"
raise TypeError(type(data))
def validate(data: object, schema: dict) -> list[str]:
errors: list[str] = []
if not isinstance(schema, dict):
return errors
types = schema.get("type")
if isinstance(types, str):
types = [types]
actual = actual_type(data)
if types and not any(t == actual or (t == "number" and actual == "integer") for t in types):
errors.append("type")
if "enum" in schema and not any(value == data for value in schema["enum"]):
errors.append("enum")
if isinstance(data, str) and "minLength" in schema and len(data) < schema["minLength"]:
errors.append("minLength")
if isinstance(data, dict) and schema.get("properties"):
for key in schema.get("required") or []:
if key not in data:
errors.append(f"required:{key}")
if schema.get("additionalProperties") is False:
for key in data:
if key not in schema["properties"]:
errors.append(f"additional:{key}")
for key, sub in schema["properties"].items():
if key in data and data[key] is not None:
errors.extend(validate(data[key], sub))
return errors
def hook_failure_gate(ctx: dict, schema: dict) -> str:
if validate(ctx, schema):
return "schema"
mode = ctx.get("onFailure")
if mode != "block":
return "not_block_mode"
if ctx.get("timedOut"):
return "timeout_block"
code = ctx.get("exitCode")
if code == 0:
return "allow"
return "block"
在 JSONNote 裏怎樣校驗 hooks settings、回調結果與審計日誌?
- 1用 Schema 固定 hooks 形狀
打開 JSON Schema。第一欄貼 hooks settings schema,第二欄貼 PreToolUse 與 onFailure block 樣本。required 或 enum 錯誤應先標紅,hook_failure_gate 不應在形狀錯誤時返回 allow 或 block。
- 2用格式化覈對 tool 調用
打開 JSON 格式化,貼 Bash tool 調用 JSON。確認 tool 與 command 鍵存在,再對照 hook_failure_gate 是否應走 block 或 allow。
- 3用 Diff 對比 allow 與 blocked 審計日誌
打開 JSON Diff。左邊放 hook 成功 allow 樣本,右邊放 decision blocked 與 reason hook_callback_failed 樣本。Diff 高亮的 hookName 就是 SIEM 規則最該鎖定的一行。
常見問題
onFailure block 與舊版 fail-open 有何不同?
舊版 hook 回調失敗時通常記錄 stderr 或警告後繼續執行 tool。2.1.317 在 settings 裏顯式 onFailure block 時,非零 exitCode 或 timeout 會寫 decision blocked 與 reason hook_callback_failed 並跳過 tool。未設 block 時仍 fail-open,hook_failure_gate 應返回 not_block_mode。
exitCode 2 與其它非零碼在日誌裏怎樣區分?
2.1.317 起 block 語義看 exitCode 是否爲零,而不是隻認 2。審計 JSON 應原樣寫整數 exitCode,SIEM 可單獨統計 2 與 1 或 127,但 hook_failure_gate 的 block 分支對所有非零 exitCode 一致。不要把 exitCode 省略成布爾 failed。
onFailure 除 block 外還有哪些取值?
常見還有 warn 或缺省 fail-open:回調失敗只告警仍執行 tool。只有 block 會觸發 decision blocked。運維在報表裏用 not_block_mode 計數 fail-open,不要把 warn 誤記爲 hook_callback_failed。
hook block 與 permission policy 層是什麼關係?
permission policy 決定 tool 類型默認 allow、deny 或 ask;hook 在 tool 執行前再跑一層腳本校驗。即使 policy 已 allow Bash,PreToolUse 失敗且 onFailure block 仍會攔截。兩套 JSON 分開保存,hook_failure_gate 只鏡像 hook 層,不替 policy 做 allow 判定。
本篇是否提供攻擊或繞過教程?
不是。本文只解釋 2.1.317 onFailure block 的 JSON 字段形狀、hook_callback_failed 審計寫法與 hook_failure_gate 對照分支,幫助運維與合規做校驗和 SIEM 歸因。不提供如何利用 hook 失敗或 exitCode 繞過管控的步驟。
總結
Claude Code 2.1.317 在 Agent SDK hook 上支持 onFailure block,回調失敗時在審計 JSON 裏寫 decision blocked、reason hook_callback_failed、hookName 與 exitCode。運維應用 hooks settings、回調結果、Bash 調用與 blocked 審計四套樣本驅動 hook_failure_gate,而不是隻盯 permission allow。
直接結論:settings 或回調 JSON 不過 schema 返回 schema;onFailure 不是 block 返回 not_block_mode;exitCode 0 返回 allow;非零 exitCode 且 block 返回 block;timedOut true 且 block 返回 timeout_block;block 與 timeout_block 的審計對齊 decision blocked、reason hook_callback_failed、hookName 與 exitCode。
下一步:用 Schema 固定 hooks settings 與回調結果,用格式化看清 Bash 調用,用 Diff 對比 allow 與 blocked 審計日誌,並在報表裏分開統計 hook_failure_gate 返回值。