Agent hooks should use structured outputs API for JSON responses

Resolved 💬 3 comments Opened Feb 3, 2026 by joebno Closed Feb 7, 2026

Agent hooks that expect JSON responses (like PreToolUse validators) fail with "hook error" because Claude models consistently wrap JSON in markdown code fences, even when explicitly instructed not to.

Current behavior

  • Agent hook prompts the model expecting raw JSON like {"ok": true}
  • Model returns ``json{"ok": true}`` (markdown-wrapped)
  • Hook fails to parse, shows "PreToolUse:Edit hook error"
  • Edit proceeds anyway (hook doesn't block)

Expected behavior

  • Agent hooks returning JSON should use the output_config.format API parameter
  • This guarantees valid, raw JSON through constrained decoding
  • No markdown wrapping, no parsing failures

Root cause

The Claude API has structured outputs specifically for this. Agent hooks should use:

output_config={
    "format": {
        "type": "json_schema",
        "schema": {
            "type": "object",
            "properties": {
                "ok": {"type": "boolean"},
                "reason": {"type": "string"}
            },
            "required": ["ok"]
        }
    }
}

Reproduction

  1. Configure an agent hook in .claude/settings.json:
{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Edit|Write",
      "hooks": [{
        "type": "agent",
        "prompt": "Validate this code change. Return ONLY raw JSON: {\"ok\": true} or {\"ok\": false, \"reason\": \"issue\"}. No markdown, no explanation.",
        "model": "haiku"
      }]
    }]
  }
}
  1. Make any edit
  2. Observe "PreToolUse:Edit hook error"

Tested with: Both Haiku and Sonnet exhibit this behavior regardless of how emphatically the prompt requests raw JSON without markdown fences.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗