Blog • Claude Code / Agent SDK

Après la mise à niveau vers Claude Code 2.1.317 avec onFailure block sur les hooks Agent SDK, comment consigner les blocages quand un callback hook échoue dans les logs JSON ?

Le 3 novembre 2026, Anthropic a livré Claude Code 2.1.317. Les callbacks hook Agent SDK ajoutent onFailure block : si un script PreToolUse sort non zéro ou time out, le client ne fail-open plus vers le tool mais bloque l action et logue blocked dans l audit JSON.

Le JSON résultat callback hook porte hookName, onFailure block, exitCode, timedOut ; l audit post-blocage : decision blocked, reason hook_callback_failed, hookName, exitCode. hook_failure_gate reflète schema, not_block_mode, allow, block, timeout_block sans confondre échec callback et deny permission policy.

La file allow et deny est dans Agent permission policy; le contexte auto evaluation Managed Agents dans évaluation auto Managed Agents; le JSON workflow de session dans Claude Code workflow. Cet article ne couvre que onFailure block 2.1.317, JSON settings hooks et résultat callback, appel Bash, champs audit hook_callback_failed et branches hook_failure_gate.

Gardez ceci : 2.1.317 change la sémantique block quand un callback hook échoue et les champs audit hook_callback_failed ; il ne réécrit pas les tables allow ou deny d une permission policy. Ensuite : changelog, JSON settings hooks, JSON résultat callback, appel Bash, logs d audit et table, source hook_failure_gate, validation dans JSONNote.

Comment 2.1.317 bloque au lieu de fail-open quand un callback hook échoue

Les notes de version sont dans changelog Claude Code, page tag version v2.1.317. Par rapport à l ancien comportement hook Agent SDK, onFailure block : PreToolUse ou scripts similaires en échec bloquent l action tool au lieu d avertir et continuer.

Le blocage intervient après le callback hook et avant l exécution tool. L audit garde decision blocked, reason hook_callback_failed, hookName, exitCode pour attribuer SIEM au hook en échec, pas un tool denied générique.

Comment écrire PreToolUse et onFailure block dans le JSON settings hooks

Sous hooks dans settings utilisateur ou projet, configurez PreToolUse avec matcher et command et onFailure block sur la même entrée. Dès 2.1.317 seul block explicite bloque le tool si callback échoue ; défaut ou autres valeurs → fail-open ou warn, hook_failure_gate not_block_mode.

Settings hooks : entrée PreToolUse onFailure block.
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "./hooks/pre-bash.sh",
            "onFailure": "block"
          }
        ]
      }
    ]
  }
}

Casse incorrecte de onFailure ou hooks absent → schema d abord avant block hook_failure_gate. Ne pas confondre forme et not_block_mode.

Comment hookName et exitCode apparaissent dans le JSON résultat callback hook

Après callback hook, le client écrit JSON résultat : hookName nomme l entrée PreToolUse, onFailure reprend block des settings, exitCode est le code de sortie, timedOut marque un kill timeout. Bash allow par permission peut être bloqué si hook échoue.

Résultat callback hook : hookName, onFailure, exitCode, timedOut.
{
  "hookName": "pre-bash.sh",
  "onFailure": "block",
  "exitCode": 2,
  "timedOut": false
}

exitCode entier, timedOut booléen ; hookName absent ou onFailure mal formé → schema. onFailure pas block → not_block_mode.

JSON Schema résultat callback hook : forme avant 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" }
  }
}

Comment le JSON d appel tool Bash avant et après blocage hook

Exemple : permission policy allow Bash ; JSON tool Bash et command npm test. PreToolUse exitCode 2 et onFailure block → pas de Bash, audit decision blocked et reason hook_callback_failed. Garder command et hookName pour branche block hook_failure_gate.

Hook réussi : appel tool Bash peut continuer.
{
  "tool": "Bash",
  "command": "npm test"
}
Hook échoué block : Bash non exécuté.
{
  "hookName": "pre-bash.sh",
  "onFailure": "continue",
  "exitCode": 2,
  "timedOut": false
}

Comment decision blocked et hook_callback_failed apparaissent dans les logs d audit

Callback hook en échec avec onFailure block : audit tool decision blocked, reason hook_callback_failed, hookName, exitCode non zéro ; timedOut true peut ajouter un flag timeout, reason reste hook_callback_failed. Ne pas mélanger deny permission et block hook dans une enum reason.

Log d audit : decision blocked, reason hook_callback_failed, hookName, exitCode.
{
  "tool": "Bash",
  "decision": "blocked",
  "reason": "hook_callback_failed",
  "hookName": "pre-bash.sh",
  "onFailure": "block",
  "exitCode": 2
}
Signal log ou appelhook_failure_gate renvoieAction opérateur
exitCode 0, timedOut false, onFailure blockallowTool autorisé ; pas hook_callback_failed dans le log.
exitCode non zéro, onFailure block, timedOut falseblockAligner sur decision blocked, reason hook_callback_failed, exitCode.
onFailure sans block ou valeur warnnot_block_modeNe pas écrire hook_callback_failed ; comparer aux rapports fail-open legacy.
timedOut true et onFailure blocktimeout_blockLogs gardent hook_callback_failed ; SIEM compte timeout_block à part.

Comment hook_failure_gate choisit entre schema et cinq branches métier

Ordre : schema settings hooks et JSON callback pour onFailure, hookName, exitCode, timedOut ; onFailure pas block → not_block_mode ; timedOut true et block → timeout_block ; exitCode zéro → allow ; exitCode non zéro et block → block ; mauvaise forme → schema.

Ne fusionnez pas block et timeout_block en un hook_failed. Comptez à part not_block_mode fail-open, block exitCode et vrai 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"

Comment valider settings hooks, résultats callback et logs d audit dans JSONNote

  1. 1
    Fixer la forme hooks avec Schema

    Ouvrez JSON Schema. Schema settings hooks au premier champ, PreToolUse et onFailure block au second. Erreurs required ou enum d abord ; pas allow ou block si forme mauvaise.

  2. 2
    Format pour les appels tool

    Ouvrez JSON Format, collez JSON appel tool Bash. Vérifiez tool et command avant block ou allow pour hook_failure_gate.

  3. 3
    Diff logs allow et blocked

    Ouvrez JSON Diff. À gauche échantillon allow hook réussi, à droite decision blocked et reason hook_callback_failed. hookName surligné est la clé SIEM.

Questions fréquentes

En quoi onFailure block diffère-t-il de l ancien fail-open ?

L ancien loguait l échec hook puis continuait le tool. Avec onFailure block explicite dès 2.1.317, exitCode non zéro ou timeout écrivent decision blocked et reason hook_callback_failed et sautent le tool. Sans block, fail-open et hook_failure_gate not_block_mode.

Comment les logs distinguent exit code 2 des autres codes non zéro ?

Dès 2.1.317 le block dépend de exitCode zéro ou non, pas seulement 2. L audit garde exitCode entier ; SIEM peut séparer 2 de 1 ou 127, hook_failure_gate block traite tout non zéro pareil. Ne pas réduire exitCode à failed booléen.

Quelles valeurs onFailure existent en dehors de block ?

Souvent warn ou défaut fail-open : échec avertit mais exécute le tool. Seul block → decision blocked. not_block_mode pour fail-open ; ne pas logger warn comme hook_callback_failed.

Quel lien entre hook block et la couche permission policy ?

La permission policy décide allow deny ask par tool ; les hooks ajoutent un script avant exécution. Même policy allow Bash, PreToolUse échoué avec onFailure block bloque. JSON séparés ; hook_failure_gate reflète seulement la couche hook.

Cet article est-il un tutoriel d attaque ou de contournement ?

Non. L article explique seulement les formes JSON onFailure block 2.1.317, champs hook_callback_failed et branches hook_failure_gate pour validation et SIEM. Pas d étapes pour contourner les contrôles via échec hook ou exitCode.

Conclusion

Claude Code 2.1.317 prend en charge onFailure block sur les hooks Agent SDK et écrit decision blocked, reason hook_callback_failed, hookName et exitCode dans l audit JSON quand les callbacks échouent. Pilotez hook_failure_gate avec settings hooks, résultats callback, appels Bash et audit blocked, pas seulement permission allow.

Décision : settings ou JSON callback échoue schema → schema ; onFailure pas block → not_block_mode ; exitCode zéro → allow ; exitCode non zéro et block → block ; timedOut true et block → timeout_block ; audits block et timeout_block alignés sur decision blocked, reason hook_callback_failed, hookName, exitCode.

Ensuite : Schema pour settings hooks et résultats callback, Format pour Bash, Diff logs allow et blocked, compter séparément les retours hook_failure_gate.

Retour au blog