Claude Agent SDK Structured Outputs: What Happens When Validation Fails?
The Claude Agent SDK can guarantee JSON-shaped output by validating against a schema and re-prompting on mismatch. That guarantee has a ceiling: a fixed retry budget. When retries run out, the SDK returns an error result with no payload, not a best-effort guess. A bot that treats any result as success will silently process nothing, or crash on a missing field. Decide up front what your bot does when that error subtype appears, and don't rely on JSON Schema 'format' keywords to do validation they don't actually perform.
AI-assisted editorial: researched and drafted with AI using the primary sources linked below. Examples are illustrative; they are not measured product results. Editorial policy.
Key takeaways
- Structured outputs validate against JSON Schema and re-prompt on mismatch, but only up to a retry limit before returning error_max_structured_output_retries with no usable payload.
- Claude Code accepts schemas with the format keyword, such as "format": "email", but treats it as an annotation and doesn't enforce it — validate those fields yourself.
- Structured outputs constrain only the final result shape. The agent can still use any tools it needs along the way, so tool access needs its own separate permission scoping.
The decision: treat the error subtype as a first-class outcome
If your bot needs the model's final answer in a specific shape — a review verdict, a list of extracted fields, a structured plan — the Claude Agent SDK can enforce that shape for you. You define a JSON Schema describing the shape of data you want, then pass it to query() via the outputFormat option (TypeScript) or output_format option (Python).
The part that's easy to miss: validation can fail permanently. The SDK validates the output against your schema, re-prompting on mismatch, but it doesn't re-prompt forever. If validation does not succeed within the retry limit, the result is an error instead of structured data. If your bot's code assumes success and reaches straight for message.structured_output, it will throw on a missing key or silently operate on undefined. The decision for this article: write the branch for the failure subtype before you ship the happy path, because the SDK will eventually produce one.
Sources: Get structured output from agents - Claude Code Docs.
How schema validation and retries actually work
The mechanism is documented consistently: define a JSON Schema for the structure you need, and the SDK validates the output against it, re-prompting on mismatch. When the agent finishes, the result message includes a structured_output field with validated data matching your schema.
In practice this means the agent still has full use of its tools while working toward the answer. The agent can use any tools it needs to complete the task, and you still get validated JSON matching your schema at the end. That's a deliberate separation: structured outputs constrain the shape of the final answer, not what the agent is allowed to do to produce it. If you need to restrict tool access as well, that's a separate permissions configuration, not something outputFormat handles.
On failure, a single-shot query() throws after yielding an error result, such as error_max_structured_output_retries. The TypeScript SDK's changelog shows this behavior has been tightened across several releases rather than being static: one fix addressed error_max_structured_output_retries being emitted when the final retry attempt succeeded, discarding valid structured output, and a later fix changed validation errors so they now name the offending key, allowed values, and actual length or count. A more recent change addressed what happens if the underlying API refuses a response outright: the result is now an error with no payload instead of structured output silently delivering a refused response. The practical lesson is that structured-output error handling has changed release to release — check the changelog for your pinned version before assuming behavior matches an older guide.
Sources: Get structured output from agents - Claude Code Docs, claude-agent-sdk-typescript/CHANGELOG.md at main · anthropics/claude-agent-sdk-typescript · GitHub.
The failure path your bot needs: error_max_structured_output_retries
Build the routine around two explicit outcomes, not one. On success, the result message's subtype is success and it carries a structured_output field with validated data matching your schema. On failure, a single-shot query() throws after yielding an error result, such as error_max_structured_output_retries.
Illustrative example: a bot that reviews pull requests and expects a schema with a verdict field (approve or request_changes) and a findings array. If the model keeps returning free text explaining its reasoning instead of the JSON object, every retry fails validation the same way. After the retry budget is spent, your bot's run loop should log the failed run, flag it for a human, and stop — not fall back to parsing the raw text with a regex, which reintroduces exactly the format drift structured outputs were meant to remove. Later SDK versions report which schema key failed validation and the allowed values, which is useful for deciding whether the schema itself needs simplifying versus the model needing a clearer instruction.
One operational note: a 2026 fix to the TypeScript SDK addressed the case where the API refused a response outright, changing structured output results so they no longer deliver a refused response's output as structured_output, returning a no-payload error instead. If you're on an older SDK version, don't assume a populated structured_output field means the model actually produced that content — check the result subtype first, every time.
Sources: Get structured output from agents - Claude Code Docs, claude-agent-sdk-typescript/CHANGELOG.md at main · anthropics/claude-agent-sdk-typescript · GitHub.
Don't rely on 'format' keywords to validate content
JSON Schema supports format annotations like email, date-time, or uri. Claude Code accepts schemas that use the format keyword, such as "format": "email", but treats format as an annotation and doesn't enforce it. If your bot's schema declares a field as format: email expecting the SDK to reject a malformed address, it won't. The model can return any string that satisfies the structural schema (a string, in the right place) and still fail a real email check.
Practical fix: validate format-sensitive fields yourself after you receive structured_output. If you're using Zod or Pydantic to build the schema in the first place, add the stricter validators and run parse() or model_validate() on the SDK's output before you trust it. That closes the gap between 'syntactically present' and 'actually correct' that schema validation alone doesn't cover.
Sources: Run Claude Code programmatically - Claude Code Docs.
What to check before you ship a structured-output routine
None of this requires exotic tooling. It requires treating the retry ceiling as a real, expected outcome of the routine rather than an edge case, and treating 'format' keywords as documentation for humans rather than enforcement for the model.
- Confirm which SDK version you're pinned to and read its changelog entries for structured outputs — retry and error-reporting behavior has changed across releases.
- Write the error_max_structured_output_retries branch first: log the raw transcript, flag for human review, and do not fall back to regex-parsing free text.
- Check the result subtype before reading structured_output, rather than assuming a populated field means success.
- Re-validate any format-sensitive fields (email, date-time, uri) yourself; the SDK does not enforce these.
- Remember structured outputs restrict the final shape only — tool access during the run needs its own permission scoping.
Primary sources
Sources checked 2026-10-22. Standards and product documentation can change; follow the linked version when implementing.
BotBento is in development. Suggest a correction.