블로그 • 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 스크립트가 non-zero 종료나 timeout이면 fail-open하지 않고 tool 동작을拦截해 감사 JSON에 blocked를 쓴다.

hook 콜백 결과 JSON에 hookName, onFailure block, exitCode, timedOut를 쓴다.拦截 후 감사 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 분기만 다룬다.

먼저 이것만: 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 콜백 실패 시 fail-open 대신 block하게 하나

변경 설명은 Claude Code changelog, 태그 페이지 v2.1.317 release. 이전 Agent SDK hook 동작 대비 onFailure block이 눈에 띈다. PreToolUse 등 hook 스크립트가 non-zero 종료나 timeout이면 경고만 남기고 진행하지 않고 tool 동작을拦截한다.

拦截은 hook 콜백 완료 후·tool 실행 전에 일어난다. 감사 JSON에 decision blocked, reason hook_callback_failed, hookName, exitCode를 남겨 일반 tool denied가 아니라 실패 hook으로 SIEM 귀속한다.

hooks settings JSON에서 PreToolUse와 onFailure block은 어떻게 쓰나

사용자 또는 프로젝트 settings의 hooks에서 PreToolUse에 matcher와 command를 쓰고 같은 항목에 onFailure block을 설정한다. 2.1.317부터 명시 block만 콜백 실패 시 tool을拦截한다. 기본값·다른 값은 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가 먼저 실패해 schema를 반환하고 hook_failure_gate block 전에 막는다. 형태 오류를 not_block_mode로 보지 않는다.

hook 콜백 결과 JSON의 hookName·exitCode 작성법

hook 콜백 완료 후 클라이언트가 결과 JSON을 쓴다. hookName은 PreToolUse 항목, onFailure는 settings block echo, exitCode는 스크립트 종료码, timedOut는 timeout kill. permission allow Bash도 hook 실패로 block될 수 있다.

hook 콜백 결과: hookName, onFailure, exitCode, timedOut.
{
  "hookName": "pre-bash.sh",
  "onFailure": "block",
  "exitCode": 2,
  "timedOut": false
}

exitCode는 정수, timedOut는 불리언이어야 한다. hookName 없거나 onFailure 형태 깨지면 hook_failure_gate는 schema. onFailure가 block이 아니면 not_block_mode.

hook 콜백 결과 JSON Schema: 형태 통과 후 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" }
  }
}

hook拦截 전후 Bash tool 호출 JSON은 어떻게 대조하나

샘플: permission policy가 Bash allow, 호출 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 분기와 대조.

hook 성공: Bash tool 호출 계속 가능.
{
  "tool": "Bash",
  "command": "npm test"
}
hook 실패 block: Bash 미실행.
{
  "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, non-zero exitCode. timedOut true면 timeout 플래그 가능하나 reason은 hook_callback_failed. permission deny와 hook block을 한 reason enum에 섞지 않는다.

감사 로그: 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 falseblockdecision blocked, reason hook_callback_failed, exitCode와 맞춘다.
onFailure block 미설정 또는 warnnot_block_modehook_callback_failed 쓰지 말고 구 fail-open 보고와 대조.
timedOut true, onFailure blocktimeout_block로그는 hook_callback_failed 유지. SIEM은 timeout_block 따로 집계.

hook_failure_gate가 schema와 다섯 business 분기를 어떻게 고르나

순서: hooks settings·콜백 결과 JSON Schema로 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, exitCode 실패 block, 진짜 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"

JSONNote에서 hooks settings, 콜백 결과, 감사 로그를 어떻게 검증하나

  1. 1
    Schema로 hooks 형태 고정

    열기 JSON Schema. 1칸에 hooks settings schema, 2칸에 PreToolUse·onFailure block 샘플. required·enum 오류를 먼저 표시하고 형태 깨지면 allow·block 반환 금지.

  2. 2
    포맷으로 tool 호출 확인

    열기 JSON 포맷, Bash tool 호출 JSON을 붙인다. tool·command 키를 확인한 뒤 hook_failure_gate가 block·allow 중 무엇인지 본다.

  3. 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을 명시하면 non-zero exitCode·timeout 시 decision blocked·reason hook_callback_failed를 쓰고 tool을 건너뛴다. block 미설정이면 fail-open 유지, hook_failure_gate는 not_block_mode.

exitCode 2와 다른 non-zero 코드를 로그에서 어떻게 구분하나?

2.1.317부터 block은 exitCode가 0인지로 본다. 감사 JSON은 정수 exitCode를 그대로 쓴다. SIEM은 2·1·127을 나눌 수 있으나 hook_failure_gate block은 non-zero면 동일. 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은 실행 전 스크립트 검사. policy가 Bash allow여도 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. non-zero exitCode·block → block. timedOut true·block → timeout_block. block·timeout_block 감사는 decision blocked, reason hook_callback_failed, hookName, exitCode와 맞춘다.

다음: Schema로 hooks settings·콜백 결과 고정, Bash 호출 포맷 확인, allow·blocked 감사 로그 Diff, hook_failure_gate 반환값을 보고서에서 분리 집계.

블로그로 돌아가기