[BUG] Headless --json-schema: model serializes an array property as XML text inside a string property, then either retry-exhausts (error_max_structured_output_retries) or silently accepts a placeholder

Open 💬 0 comments Opened Jul 13, 2026 by adamgsg

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

Summary
When running headless (claude -p) with --output-format json --json-schema <schema>, for a task whose result is a moderately large structured object (a long string field plus an array of objects), the model reliably serializes the array as XML / tool-call-style text inside the first string property and never populates the array property. Every internal StructuredOutput attempt fails schema validation with must have required property '<array_field>', and the run ends either as subtype: "error_max_structured_output_retries" (strict schema) or subtype: "success" with a degenerate placeholder (loose schema) that's indistinguishable from a real result. (We've literally seen it output "placeholder" or "test" in order to pass the schema check)

Environment
Claude Code 2.1.204, headless (-p), --output-format json --json-schema, --max-turns 100, --permission-mode bypassPermissions, MCP servers attached
Auth: Claude subscription OAuth token
Reproduced on claude-opus-4-8 and claude-sonnet-5

What the transcript shows
The result object only contains the last attempt; the repeated failures are in the session transcript. In one run the model made 5 StructuredOutput calls, each with narrative (required string) = a correct ~5,000-char answer whose tail held the array serialized as XML (…</narrative>\n<findings>\n[ {…} ]\n</findings>\n</invoke>), and the required findings array absent — rejected each time with must have required property 'findings'. The content was correct; it was just placed in the wrong field. The generic error never tells the model the array must be a top-level property, so it repeats the mistake until retries exhaust.

Why it's easy to miss
When it degrades to a placeholder that passes a loose schema, the top-level result shows subtype:"success", is_error:false, terminal_reason:"completed", stop_reason:"tool_use" — no signal

Note, this is somewhat similar to related issues #70645/#76901/#74232 but this is the headless claude -p --json-schema path, whereas those are all the Workflow-tool agent({schema}) path

What Should Happen?

What should happen?
When --json-schema is supplied, the model's produced content should be captured faithfully into the schema fields. Specifically:

An array property (e.g. findings) should be populated as a top-level array, not serialized as XML/text inside a sibling string property. When the model does embed an array's content inside another string, the harness should recognize and correct it — ideally by re-prompting with a specific message (e.g. "emit findings as a top-level array, not inside narrative") instead of the generic must have required property error, which just produces identical retries.
If the model cannot produce schema-valid output after its retries, the run should fail detectably — is_error: true and/or a distinct error subtype — and never return subtype: "success" with a degenerate placeholder that a caller cannot distinguish from a real result.
A completed run reported as subtype: "success" should mean structured_output reflects the model's actual work, not a stub emitted purely to satisfy validation.

Error Messages/Logs

Steps to Reproduce

Save this generic schema as schema.json:

{
  "type": "object",
  "required": ["narrative", "findings"],
  "additionalProperties": false,
  "properties": {
    "narrative": { "type": "string" },
    "findings": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["summary", "details"],
        "additionalProperties": false,
        "properties": {
          "summary": { "type": "string" },
          "details": { "type": "string" }
        }
      }
    }
  }
}

Write a prompt that asks the model to analyze a moderately large input (e.g. a large multi-file diff or a long document) and return a long narrative plus several findings objects. The bigger the intended output, the more reliably it reproduces.

Run headless with schema-constrained output:

claude -p "<prompt>" --model claude-opus-4-8 --output-format json --json-schema schema.json --max-turns 100 > out.json

Inspect the result:

jq '{subtype, is_error, terminal_reason, structured_output}' out.json

The run ends either as subtype: "error_max_structured_output_retries" (strict schema), or subtype: "success" whose structured_output is a degenerate placeholder (loose schema).

(Optional) Confirm the root cause in the session transcript at ~/.claude/projects/<cwd>/<session_id>.jsonl (session id is .session_id from out.json):

grep '"name":"StructuredOutput"' <transcript>.jsonl

Each attempt's narrative string contains the findings serialized as XML (…</narrative>\n<findings>\n[ … ]\n</findings>\n</invoke>) while the top-level findings property is absent — so every attempt is rejected with must have required property 'findings'.

Claude Model

Not sure / Multiple models

Is this a regression?

Yes, this worked in a previous version

Last Working Version

_No response_

Claude Code Version

2.1.204

Platform

Anthropic API

Operating System

Ubuntu/Debian Linux

Terminal/Shell

iTerm2

Additional Information

Note, this seems to have been working _better_ until the week of July 6, that's when we really noticed it started failing consistently. No changes on our end.

View original on GitHub ↗