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.formatAPI 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
- 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"
}]
}]
}
}
- Make any edit
- 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.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗