Streaming, Errors & Resilience

Structured Output Truncation: When stop_reason max_tokens Yields Unparseable JSON

Structured outputs guarantee schema-valid JSON — but only for responses that finish. A response cut off at the max_tokens ceiling is valid JSON with the ending missing, and no amount of clever repair makes it trustworthy.

Claude 3P 101 · Updated July 2026 · Unofficial guide

Structured outputs let you pin Claude's response to a JSON Schema: pass output_config: {"format": {"type": "json_schema", "schema": {...}}} and the API constrains generation so the output conforms. Teams reasonably read that as "parsing can never fail." It can — in one specific, documented way. The max_tokens parameter is a hard ceiling on how many tokens the model may generate, and when generation hits that ceiling mid-object, the API returns HTTP 200 with stop_reason: "max_tokens" and whatever JSON had been produced so far. A truncated object is missing closing braces, required fields, or half a string value, so it fails both JSON parsing and schema validation.

Why "repair" is the wrong instinct

The tempting fix is to patch the fragment — append closing braces, drop the last incomplete field, fill required keys with nulls. Anthropic's documentation is explicit that the correct handling is different: output cut off by max_tokens may be incomplete or invalid, so retry the request with a higher max_tokens value. The reasons hold up under scrutiny:

You cannot tell what is missing. A truncated array might have contained two more elements; a truncated string might have ended mid-sentence in a way that inverts its meaning. Syntactic repair produces something parseable, not something correct.

The schema guarantee only covers completed generations. The constrained decoding that makes structured outputs work steers the model token by token toward a valid document. Cutting that process short leaves you outside the guarantee, and downstream code that assumes "schema-valid" is now consuming fabricated defaults.

Check stop_reason before you parse

The robust pattern is to branch on stop_reason before touching content:

stop_reasonMeaningAction
end_turnGeneration completed normallyParse and validate as usual
max_tokensHit the token ceiling mid-outputRetry with a larger max_tokens
refusalModel declined the request (still HTTP 200, still billed)Handle separately — output may not match the schema
from anthropic import Anthropic

client = Anthropic()
max_tokens = 1024
for _ in range(3):
    msg = client.messages.create(
        model="claude-sonnet-5",
        max_tokens=max_tokens,
        output_config={"format": {"type": "json_schema", "schema": SCHEMA}},
        messages=[{"role": "user", "content": prompt}],
    )
    if msg.stop_reason != "max_tokens":
        break
    max_tokens *= 2  # truncated JSON: retry larger, never repair

The Python SDK's client.messages.parse() helper (with Pydantic models) validates the response client-side as well, which turns a silently truncated object into a loud failure — exactly what you want. Refusals deserve their own branch; see the refusal stop_reason article.

The thinking-token trap

On models with thinking enabled, max_tokens caps thinking plus response text combined. A budget that comfortably fits your largest expected JSON document can still truncate it if the model spends most of the budget reasoning first. If structured-output truncation appears on requests where the JSON itself is small, the fix is the same retry-with-more-headroom, or a lower effort setting to shrink thinking. The thinking/max_tokens trap covers this interaction in depth.

Rule of thumb: treat stop_reason: "max_tokens" on a structured-output request as a retryable capacity error in your own application logic — same shape as a 429, except the parameter you raise is max_tokens. One nuance: max_tokens has no rate-limit downside, because OTPM counts only tokens actually generated. Setting it generously costs nothing until the model actually uses it.

Structured outputs work with streaming too, but the same rule applies: you cannot validate until the final event arrives, so buffer the fragments and check the stop_reason delivered in message_delta before parsing.

Where to go next

For the feature itself — supported schemas, strict tool use, and platform availability — start with the structured outputs overview, or browse the feature matrix.

Sources