[Bug] --json and --output-schema are silently ignored when tools/MCP servers are active, resulting in malformed outputs
What version of Codex CLI is running?
codex-cli 0.116.0
What subscription do you have?
api
Which model were you using?
gpt-5.4-codex xhigh
What platform is your computer?
Darwin 25.2.0 arm64 arm
What terminal emulator and version are you using (if applicable)?
ghostty
What issue are you seeing?
When using the --json flag combined with --output-schema, the CLI is expected to strictly output parseable JSON that adheres to the provided schema. This works perfectly in normal prompts.
However, the moment we include MCP servers or tools in the request context (e.g., -c mcp_servers.local.command="..."), the model completely ignores the --output-schema constraint. Instead of valid JSON, the CLI frequently returns malformed outputs, such as:
YAML-like bare objects missing the outer {} braces.
Missing commas at the end of properties (e.g., key: "value"\nkey2: "value2").
Unquoted keys and occasional markdown block wrappers (```json).
This indicates that response_format: { strict: true } is either being silently dropped by the CLI or rejected by the backend API when tools are present, causing a silent fallback to raw, unconstrained text generation.
What steps can reproduce the bug?
- Create a strict
schema.json:
{
"type": "object",
"properties": {
"status": { "type": "string" },
"score": { "type": "number" }
},
"required": ["status", "score"],
"additionalProperties": false
}
- Run
codex execwith tools enabled:
codex exec --json --output-schema schema.json -c mcp_servers.test.command="echo" -m gpt-5.4 "Return a status of UNSURE and a score of 6.5"
- Observe the malformed
stdout. Instead of{"status": "UNSURE", "score": 6.5}, it often prints a bare object without braces or commas:
status: "UNSURE"
score: 6.5
What is the expected behavior?
The CLI should enforce the --output-schema strictly, even when tools/MCP servers are active in the trajectory.
If this is a hard imitation from the upstream API (i.e. tools and structured outputs cannot be mixed natively), the CLI should either:
- Automatically strip tools during the final generation turn to ensure strict JSON adherence.
- Fail loudly and throw a clear error informing the user that tools + strict schema cannot be used simultaneously, rather than silently degrading the output.
Additional information
This bug severely breaks automated agentic workflows and wrappers built around codex exec, as we shouldn't need to write complex Regular Expressions to manually inject {} and commas just to recover the stdout. Native Claude integrations handle this tool + structured output constraint flawlessly out of the box.
7 Comments
Fix for Codex CLI JSON Schema when tools are active
Root Cause
When
--output-schemais provided and tools are active, the Codex CLI uses the internal/backend-api/responsesendpoint. This internal endpoint expects a flattened metadata format, which differs from the public/v1/chat/completionsAPI. Currently, if strictjson_schemarules are passed to this endpoint while tools are simultaneously active, the backend silently drops or degrades the strict constraint, allowing the model to hallucinate or fallback to unformatted text (such as YAML) instead of valid JSON.Proposed Solution: Synthetic Tool Interceptor
To enforce strict schema output natively without relying on soft prompt-engineering, we implemented a structural bypass.
codex-rs/core/src/codex.rs):When
turn_context.final_output_json_schemaexists and tools are populated, we dynamically inject a synthetic tool (__codex_submit_result__) into the pipeline. The parameters for this tool are strictly defined to match the provided--output-schema.codex-rs/core/src/stream_events_utils.rs):In the stream event handler (
handle_output_item_done), we explicitly intercept calls targeting__codex_submit_result__. Instead of dispatching this virtual tool to theToolRouter(which would fail), we safely unpack the structurally-enforced JSON parameter payload and surface it upstream as a standardResponseItem::Message.This ensures rock-solid API-level enforcement of the requested JSON structured output, regardless of whether MCP servers or runtime CLI tools are present.
<details>
<summary><b>Click to expand the proposed code changes</b></summary>
</details>
Thanks for the bug report. Do you believe this is a regression? If so, which version is the last one where this works?
Hi @etraut-openai, thanks for looking into this!
I don't believe this is a recent regression in the CLI codebase. Instead, it seems to be an underlying architectural limitation (or bug) in the
/backend-api/responsesendpoint itself.Whenever
tools(such as MCP servers or local shell handlers) are attached to the request payload, the backend API silently degrades or completely ignores thetext.format.schemastrict validation directive. Because of this backend behavior, the model's base directives bleed through, resulting in markdown wrappers (```json ...```) or unquoted YAML-like outputs instead of the strictly enforced JSON Schema.If the CLI ever seamlessly supported both
--jsonand--mcptools simultaneously in past versions, the backend might have handled tool context differently back then. However, currently, the only reliable way to enforce the schema while tools are active is by using a "hard" structural bypass. This is why the proposed fix (in the linked PR) dynamically injects a synthetic tool (__codex_submit_result__) to trick the model into strictly honoring the schema via function parameter coercion.Prepared a maintainer-ready branch for this issue, but I took a different path from the synthetic-tool proposal.
My read is that the CLI still sends the requested schema; the silent failure is that with tools/MCP active the final assistant message can degrade to YAML/plain text and
codex execstill exits 0.This branch fixes that at the
codex execboundary:--output-schemabefore printing final outputadditionalProperties, exit non-zero instead of silently succeedingerrorevent in--jsonmode--output-last-messageon validation failureI intentionally avoided both synthetic tool injection and a global
tools + output_schemaprohibition.Ready branch:
Validation:
cargo check -p codex-exec --testscargo test -p codex-exec --no-runcodex-execunit/integration test binaries directly; all passedjust fix -p codex-execjust fmtgit diff --checkjust argument-comment-lintblocked locally because Bazel / the script dependency is unavailable hereOn the regression question: I have not confirmed a last-known-good version, so I would not call this a verified regression.
I think the CLI harness is doing the right thing here. This appears to be a model behavior issue. We'll share the feedback with the team responsible for training our codex models.
@JaysonGeng, I don't think we should work around a model issue in the harness like you're proposing here. If you need to work around the problem in your use case, you can wrap the call to
codex execwith schema validation logic.This looks right.
But it breaks.
Here’s what’s happening.
The model is returning key/value lines.
Not real JSON.
Example:
That breaks because there are no braces.
And there is no comma between properties.
Fixed version:
That’s the whole problem.
If this is happening a lot, a JSON repair step can clean up cases like this before
JSON.parse()sees them.Might a better workaround be to expose a MCP tool and prompt the agent to invoke that with its required input schema instead?