Блог • 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.

Сначала это: 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 settings: запись PreToolUse onFailure block.
{
  "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.

Результат callback hook: hookName, onFailure, exitCode, timedOut.
{
  "hookName": "pre-bash.sh",
  "onFailure": "block",
  "exitCode": 2,
  "timedOut": false
}

exitCode — integer, timedOut — boolean; нет hookName или плохая форма onFailure → schema. onFailure не block → not_block_mode.

JSON Schema результата callback hook: форма до hook_failure_gate.
{
  "$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.

Hook успешен: вызов tool Bash может продолжиться.
{
  "tool": "Bash",
  "command": "npm test"
}
Hook failed block: Bash не выполнялся.
{
  "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.

Audit log: decision blocked, reason hook_callback_failed, hookName, exitCode.
{
  "tool": "Bash",
  "decision": "blocked",
  "reason": "hook_callback_failed",
  "hookName": "pre-bash.sh",
  "onFailure": "block",
  "exitCode": 2
}
Сигнал лога или вызоваhook_failure_gate возвращаетДействие оператора
exitCode 0, timedOut false, onFailure blockallowTool может выполняться; в логе не нужен hook_callback_failed.
Non-zero exitCode, onFailure block, timedOut falseblockСогласовать с decision blocked, reason hook_callback_failed, exitCode.
onFailure без block или значение warnnot_block_modeНе писать hook_callback_failed; сверять с legacy fail-open отчётами.
timedOut true и onFailure blocktimeout_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.

hook_failure_gate: schema, not_block_mode, allow, 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"

Как проверять hooks settings, результаты callback и audit logs в JSONNote

  1. 1
    Зафиксировать форму hooks через Schema

    Откройте JSON Schema. Schema hooks settings в первое поле, PreToolUse и onFailure block во второе. Ошибки required или enum сначала; при плохой форме не allow или block от hook_failure_gate.

  2. 2
    Format для вызовов tool

    Откройте JSON Формат, вставьте JSON вызова tool Bash. Проверьте tool и command, затем block или allow у hook_failure_gate.

  3. 3
    Diff 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.

Назад в блог