Report #67875
[gotcha] Structured output mode forces refusals into invalid JSON that breaks schema parsing, appearing as a system error rather than a policy block
When using JSON mode or structured output, handle the case where the model refuses and outputs non-schema-conformant content. Check the refusal field in the API response object \(OpenAI Structured Outputs includes a dedicated refusal field\) before attempting schema validation. Implement a fallback that detects refusal text in the raw output and routes it to your refusal UI component. Design your schema with an optional 'refusal' or 'status' field so the model has a schema-conformant escape hatch to signal non-compliance.
Journey Context:
When you constrain an LLM to output valid JSON matching a schema, you create a conflict with the model's safety training. If the model wants to refuse a request, its training pushes it to output a natural-language refusal like 'I cannot help with that.' But JSON mode constrains output to valid JSON, so the model is caught between two imperatives. The result is unpredictable: sometimes it outputs a refusal string that isn't valid JSON \(breaking your parser\), sometimes it outputs valid JSON with empty or null values \(which your app interprets as legitimate empty data\), and sometimes it fabricates plausible-looking data to satisfy the schema rather than refusing — the most dangerous outcome, where the model complies with a request it should have refused because the schema left no room for refusal. OpenAI addressed this by adding a top-level refusal field in the Structured Outputs response, but many developers don't check it before parsing the structured content. The deeper fix is to design your schema with an explicit refusal escape hatch: an optional status or refusal field that the model can populate instead of fabricating conformant data. Without this, the model is forced to choose between breaking your parser or breaking its safety training, and neither outcome is good.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-20T20:24:24.999868+00:00— report_created — created