Блог • Claude Code / Agent SDK
После обновления до Claude Code 2.1.317 с onFailure block у Agent SDK hook: как фиксировать блокировку при сбое callback hook в JSON-логах?
3 ноября 2026 Anthropic выпустил Claude Code 2.1.317. Callbacks Agent SDK hook получают onFailure block: при non-zero exit или timeout скрипта PreToolUse клиент не fail-open к tool, а блокирует action и пишет blocked в audit JSON.
JSON результата callback hook: hookName, onFailure block, exitCode, timedOut; audit JSON после block: decision blocked, reason hook_callback_failed, hookName, exitCode. hook_failure_gate отражает schema, not_block_mode, allow, block, timeout_block, не путая сбой callback с permission policy deny.
Очередь allow и deny — в Agent permission policy; контекст auto evaluation Managed Agents — в auto evaluation Managed Agents; JSON workflow сессии — в Claude Code workflow. Эта статья только о onFailure block 2.1.317, hooks settings и JSON результата callback, вызове Bash, полях audit hook_callback_failed и ветвлении hook_failure_gate.
- Записи PreToolUse в hooks settings требуют onFailure block для блокировки tool при сбое callback; без block → hook_failure_gate not_block_mode, без reason hook_callback_failed.
- JSON результата callback: hookName, onFailure, integer exitCode, boolean timedOut; exitCode 0 и timedOut false → allow, non-zero exitCode с onFailure block → block.
- Audit logs в ветке block: decision blocked, reason hook_callback_failed, hookName, exitCode; SIEM агрегирует по hookName, не смешивая deny permission policy.
- timedOut true и onFailure block → hook_failure_gate timeout_block; плохая форма settings или JSON callback → schema до block logic.
Сначала это: 2.1.317 меняет семантику block при сбое callback hook и поля audit hook_callback_failed; таблицы allow или deny в permission policy не переписываются. Далее: changelog, hooks settings JSON, JSON результата callback, вызов Bash, audit logs и таблица, исходник hook_failure_gate, проверка форм в JSONNote.
Как 2.1.317 блокирует вместо fail-open при сбое callback hook
Изменения — в changelog Claude Code, страница тега релиз v2.1.317. По сравнению со старым поведением Agent SDK hook заметен onFailure block: PreToolUse при non-zero exit или timeout блокирует tool action, а не только предупреждает и продолжает.
Блок после callback hook и до выполнения tool. Audit JSON: decision blocked, reason hook_callback_failed, hookName, exitCode для SIEM-атрибуции к упавшему hook, не общий tool denied.
Как задать PreToolUse и onFailure block в hooks settings JSON
В hooks user или project settings настройте PreToolUse с matcher и command и onFailure block в той же записи. С 2.1.317 только явный block останавливает tool при сбое callback; default или другие значения → fail-open или warn, hook_failure_gate not_block_mode.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "./hooks/pre-bash.sh",
"onFailure": "block"
}
]
}
]
}
}
Неверный регистр onFailure или нет hooks → schema сначала, до block hook_failure_gate. Не считать ошибку формы not_block_mode.
Как hookName и exitCode выглядят в JSON результата callback hook
После callback hook клиент пишет JSON результата: hookName — запись PreToolUse, onFailure echo block из settings, exitCode — код выхода скрипта, timedOut — kill по timeout. Bash с permission allow может block при сбое hook.
{
"hookName": "pre-bash.sh",
"onFailure": "block",
"exitCode": 2,
"timedOut": false
}
exitCode — integer, timedOut — boolean; нет hookName или плохая форма onFailure → schema. onFailure не block → not_block_mode.
{
"$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" }
}
}
Как выглядит JSON вызова tool Bash до и после block hook
Образец: permission policy allow Bash; JSON tool Bash и command npm test. PreToolUse exitCode 2 и onFailure block → без Bash, audit decision blocked и reason hook_callback_failed. Сохраните command и hookName для ветки block hook_failure_gate.
{
"tool": "Bash",
"command": "npm test"
}
{
"hookName": "pre-bash.sh",
"onFailure": "continue",
"exitCode": 2,
"timedOut": false
}
Как писать decision blocked и hook_callback_failed в audit logs
Сбой callback hook с onFailure block: audit tool decision blocked, reason hook_callback_failed, hookName, non-zero exitCode; при timedOut true можно flag timeout, reason остаётся hook_callback_failed. Не смешивайте permission deny и hook block в одной reason enum.
{
"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. |
| Non-zero exitCode, onFailure block, timedOut false | block | Согласовать с decision blocked, reason hook_callback_failed, exitCode. |
| onFailure без block или значение warn | not_block_mode | Не писать hook_callback_failed; сверять с legacy fail-open отчётами. |
| timedOut true и onFailure block | timeout_block | В логах hook_callback_failed; SIEM считает timeout_block отдельно. |
Как hook_failure_gate выбирает между schema и пятью business-ветками
Порядок: schema hooks settings и JSON callback для onFailure, hookName, exitCode, timedOut; onFailure не block → not_block_mode; timedOut true и block → timeout_block; exitCode 0 → allow; non-zero exitCode и block → block; плохая форма → schema.
Не схлопывайте block и timeout_block в один hook_failed. Считайте отдельно not_block_mode fail-open, block по exitCode и настоящий 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"
Как проверять hooks settings, результаты callback и audit logs в JSONNote
- 1Зафиксировать форму hooks через Schema
Откройте JSON Schema. Schema hooks settings в первое поле, PreToolUse и onFailure block во второе. Ошибки required или enum сначала; при плохой форме не allow или block от hook_failure_gate.
- 2Format для вызовов tool
Откройте JSON Формат, вставьте JSON вызова tool Bash. Проверьте tool и command, затем block или allow у hook_failure_gate.
- 3Diff audit logs allow и blocked
Откройте JSON Diff. Слева образец allow при успехе hook, справа decision blocked и reason hook_callback_failed. Подсвеченный hookName — ключ SIEM.
Вопросы
Чем onFailure block отличается от старого fail-open?
Legacy при сбое hook логировал и продолжал tool. С onFailure block в settings с 2.1.317 non-zero exitCode или timeout пишут decision blocked и reason hook_callback_failed и пропускают tool. Без block — fail-open, hook_failure_gate not_block_mode.
Как в логах отличить exit code 2 от других non-zero?
С 2.1.317 block зависит от нулевого exitCode, не только 2. Audit JSON хранит integer exitCode как есть; SIEM может делить 2 и 1 или 127, hook_failure_gate block одинаков для любого non-zero. Не сводите exitCode к boolean failed.
Какие значения onFailure кроме block?
Часто warn или default fail-open: сбой предупреждает, tool выполняется. Только block → decision blocked. not_block_mode для fail-open; не логируйте warn как hook_callback_failed.
Как hook block связан со слоем permission policy?
permission policy решает allow deny ask для tool; hooks — скрипт до выполнения. Даже policy allow Bash, сбой PreToolUse с onFailure block останавливает вызов. JSON раздельно; hook_failure_gate только слой hook, не policy allow.
Есть ли в статье атака или bypass?
Нет. Статья только о формах JSON onFailure block 2.1.317, полях hook_callback_failed и ветках hook_failure_gate. Без шагов обхода контролей через сбой hook или exitCode.
Итог
Claude Code 2.1.317 поддерживает onFailure block у Agent SDK hook и при сбое callback пишет в audit JSON decision blocked, reason hook_callback_failed, hookName и exitCode. Кормите hook_failure_gate образцами hooks settings, callback, Bash и blocked audit, а не только permission allow.
Решение: settings или callback JSON не проходит schema → schema; onFailure не block → not_block_mode; exitCode 0 → allow; non-zero exitCode и block → block; timedOut true и block → timeout_block; audit block и timeout_block согласованы с decision blocked, reason hook_callback_failed, hookName, exitCode.
Дальше: Schema для hooks settings и callback, Format для Bash, Diff audit logs allow и blocked, отдельно отчитывать возвраты hook_failure_gate.