tool_mode=code_mode_only drops sibling properties from MCP schemas with root-level oneOf
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 125k
- Forks
- 19.4k
- PR merge metrics
- PR metrics pending
Description
What version of Codex CLI is running?
codex-cli 0.152.1
What subscription do you have?
ChatGPT Enterprise
Which model were you using?
gpt-5.6-sol
What platform is your computer?
Darwin 25.6.0 arm64 arm
What terminal emulator and version are you using (if applicable)?
Apple Terminal 470.2. The reproducer uses non-interactive codex exec.
Codex doctor report
{
"schemaVersion": 1,
"generatedAt": "1788356502s since unix epoch",
"overallStatus": "warning",
"codexVersion": "0.152.1",
"checks": {
"app_server.status": {
"id": "app_server.status",
"category": "app-server",
"status": "ok",
"summary": "background server is not running",
"details": {
"control socket": "/Users/davrig01/.codex/app-server-control/app-server-control.sock",
"daemon state dir": "/Users/davrig01/.codex/app-server-daemon",
"mode": "ephemeral",
"pid file": "/Users/davrig01/.codex/app-server-daemon/app-server.pid (missing)",
"settings": "/Users/davrig01/.codex/app-server-daemon/settings.json (missing)",
"status": "not running",
"update-loop pid file": "/Users/davrig01/.codex/app-server-daemon/app-server-updater.pid (missing)"
},
"remediation": null,
"durationMs": 0
},
"auth.credentials": {
"id": "auth.credentials",
"category": "auth",
"status": "ok",
"summary": "auth is configured",
"details": {
"auth env vars present": "OPENAI_API_KEY",
"auth file": "/Users/davrig01/.codex/auth.json",
"auth storage mode": "File",
"stored API key": "true",
"stored ChatGPT tokens": "false",
"stored agent identity": "false",
"stored auth mode": "api_key"
},
"remediation": null,
"durationMs": 0
},
"config.load": {
"id": "config.load",
"category": "config",
"status": "ok",
"summary": "config loaded",
"details": {
"CODEX_HOME": "/Users/davrig01/.codex",
"config.toml": "/Users/davrig01/.codex/config.toml",
"config.toml parse": "ok",
"cwd": "/Users/davrig01/Downloads",
"enabled feature flags": "shell_tool, view_image, sleep_tool, unified_exec, unified_exec_zsh_fork, shell_snapshot, content_item_kinds, code_mode_host, terminal_resize_reflow, sqlite, memories, hooks, enable_request_compression, unbounded_connection_retries, multi_agent, apps, tool_search_always_defer_mcp_tools, tool_suggest, plugins, in_app_browser, in_app_chat, in_app_dictation, in_app_local_automation, in_app_updates, browser_use, browser_use_full_cdp_access, browser_use_external, computer_use, remote_plugin, plugin_sharing, image_generation, resize_all_images, item_ids, skill_mcp_dependency_install, skill_search, mentions_v2, steer, guardian_approval, goals, collaboration_modes, tool_call_mcp_elicitation, auth_elicitation, personality, fast_mode, tui_app_server, remote_compaction_v2, compaction_image_budget, workspace_dependencies",
"feature flag overrides": "memories=true",
"feature flags enabled": "48",
"log dir": "/Users/davrig01/.codex/log",
"mcp servers": "10",
"model": "gpt-5.6-sol",
"model provider": "proxy",
"sqlite home": "/Users/davrig01/.codex"
},
"remediation": null,
"durationMs": 0
},
"desktop.app.version": {
"id": "desktop.app.version",
"category": "desktop",
"status": "ok",
"summary": "the desktop application is installed",
"details": {
"log directory": "$HOME/Library/Logs/com.openai.codex",
"running": "true",
"version": "26.825.51511"
},
"remediation": null,
"durationMs": 0
},
"desktop.app_server.handshake": {
"id": "desktop.app_server.handshake",
"category": "desktop",
"status": "ok",
"summary": "no desktop app-server handshake was recorded",
"details": {},
"remediation": null,
"durationMs": 0
},
"desktop.security.enforcement": {
"id": "desktop.security.enforcement",
"category": "desktop",
"status": "ok",
"summary": "the desktop application passed available macos security assessments",
"details": {
"gatekeeper": "accepted"
},
"remediation": null,
"durationMs": 0
},
"git.environment": {
"id": "git.environment",
"category": "git",
"status": "ok",
"summary": "git version 2.50.1 (Apple Git-155)",
"details": {
"PATH git #1": "/usr/bin/git",
"PATH git entries": "1",
"git build options": "git version 2.50.1 (Apple Git-155); cpu: arm64; no commit associated with this build; sizeof-long: 8; sizeof-size_t: 8; shell-path: /bin/sh; feature: fsmonitor--daemon; libcurl: 8.7.1; zlib: 1.2.12; SHA-1: SHA1_DC; SHA-256: SHA256_BLK",
"git exec path": "/Applications/Xcode.app/Contents/Developer/usr/libexec/git-core",
"git version": "git version 2.50.1 (Apple Git-155)",
"repo detected": "false",
"selected git": "/usr/bin/git"
},
"remediation": null,
"durationMs": 42
},
"installation": {
"id": "installation",
"category": "install",
"status": "ok",
"summary": "installation looks consistent",
"details": {
"PATH codex #1": "/Users/davrig01/.local/bin/aarch64/codex",
"current executable": "/Users/davrig01/.local/bin/aarch64/codex",
"install context": "other",
"managed by bun": "false",
"managed by npm": "false",
"managed by pnpm": "false",
"managed package root": "not set"
},
"remediation": null,
"durationMs": 12
},
"mcp.config": {
"id": "mcp.config",
"category": "mcp",
"status": "ok",
"summary": "MCP configuration is locally consistent",
"details": {
"configured servers": "10",
"disabled servers": "4",
"stdio servers": "6",
"streamable_http servers": "4"
},
"remediation": null,
"durationMs": 589
},
"network.env": {
"id": "network.env",
"category": "network",
"status": "ok",
"summary": "network-related environment looks readable",
"details": {
"managed proxy": "not configured",
"proxy env vars": "none",
"respect system proxy": "disabled",
"system proxy": "direct"
},
"remediation": null,
"durationMs": 1
},
"network.provider_reachability": {
"id": "network.provider_reachability",
"category": "reachability",
"status": "ok",
"summary": "active provider endpoints are reachable over HTTP",
"details": {
"desktop assets CDN": "https://chatgpt.com/backend-api/<redacted> reachable (HTTP 200)",
"proxy API inference URL": "https://openai-api-proxy.geo.arm.com/api/<redacted> reachable (HTTP 404)",
"proxy API route probe": "https://openai-api-proxy.geo.arm.com/api/<redacted> route exists (HTTP 401)",
"reachability mode": "API key auth"
},
"remediation": null,
"durationMs": 220
},
"network.websocket_reachability": {
"id": "network.websocket_reachability",
"category": "websocket",
"status": "ok",
"summary": "Responses WebSocket is not enabled for the active provider",
"details": {
"model provider": "proxy",
"provider name": "OpenAI",
"proxy env vars": "none",
"supports websockets": "false",
"wire API": "responses"
},
"remediation": null,
"durationMs": 0
},
"runtime.provenance": {
"id": "runtime.provenance",
"category": "runtime",
"status": "ok",
"summary": "running local build on macos-aarch64",
"details": {
"commit": "unknown",
"current executable": "/Users/davrig01/.local/bin/aarch64/codex",
"install method": "other",
"platform": "macos-aarch64",
"version": "0.152.1"
},
"remediation": null,
"durationMs": 0
},
"runtime.search": {
"id": "runtime.search",
"category": "search",
"status": "ok",
"summary": "search is OK (system)",
"details": {
"search command": "rg",
"search command readiness": "ripgrep 15.2.0 (rev e89fff89ac)",
"search provider": "system"
},
"remediation": null,
"durationMs": 124
},
"sandbox.helpers": {
"id": "sandbox.helpers",
"category": "sandbox",
"status": "ok",
"summary": "sandbox configuration is readable",
"details": {
"approval policy": "OnRequest",
"codex-linux-sandbox helper": "none",
"execve wrapper helper": "/Users/davrig01/.codex/tmp/arg0/codex-arg0AJrZ6Z/codex-execve-wrapper",
"filesystem sandbox": "restricted",
"network sandbox": "restricted"
},
"remediation": null,
"durationMs": 0
},
"security.endpoint": {
"id": "security.endpoint",
"category": "security",
"status": "warning",
"summary": "endpoint protection detected; Codex exclusions are unverified",
"details": {
"Codex exclusions": "not verified",
"endpoint products": "CrowdStrike Falcon",
"exclusion targets": "signing team 2DC432GLL2 plus the installed Codex app identity, signed codex agent, and required helpers"
},
"issues": [
{
"severity": "warning",
"cause": "CrowdStrike Falcon can interfere with Codex. Verify Codex exclusions.",
"measured": "not verified",
"expected": "Codex application and helper exclusions",
"remedy": "CrowdStrike Falcon: Add a certificate or IOA exclusion for Codex. If sensor overhead continues, exclude the Codex agent from sensor visibility. Keep monitoring descendant processes.",
"fields": [
"Codex exclusions"
]
}
],
"remediation": "ask your security administrator to verify Codex exclusions and required helper allowances",
"durationMs": 22
},
"state.paths": {
"id": "state.paths",
"category": "state",
"status": "ok",
"summary": "state paths and databases are inspectable",
"details": {
"CODEX_HOME": "/Users/davrig01/.codex (dir)",
"active rollout files": "4643 files, 4958143346 total bytes, 1067874 average bytes",
"archived rollout files": "18 files, 8411346 total bytes, 467297 average bytes",
"goals DB": "/Users/davrig01/.codex/goals_1.sqlite (file)",
"goals DB integrity": "ok",
"log DB": "/Users/davrig01/.codex/logs_2.sqlite (file)",
"log DB integrity": "ok",
"log dir": "/Users/davrig01/.codex/log (dir)",
"memories DB": "/Users/davrig01/.codex/memories_1.sqlite (file)",
"memories DB integrity": "ok",
"queue DB": "/Users/davrig01/.codex/queue_1.sqlite (file)",
"queue DB integrity": "ok",
"sqlite home": "/Users/davrig01/.codex (dir)",
"state DB": "/Users/davrig01/.codex/state_5.sqlite (file)",
"state DB integrity": "ok",
"thread history DB": "/Users/davrig01/.codex/thread_history_1.sqlite (file)",
"thread history DB integrity": "ok"
},
"remediation": null,
"durationMs": 1468
},
"state.rollout_db_parity": {
"id": "state.rollout_db_parity",
"category": "threads",
"status": "warning",
"summary": "rollout files and state DB thread inventory differ",
"details": {
"default model provider": "proxy",
"rollout DB active files": "4643",
"rollout DB active rows": "1520",
"rollout DB archive mismatches": "0",
"rollout DB archived files": "18",
"rollout DB archived rows": "18",
"rollout DB duplicate DB paths": "0",
"rollout DB duplicate rollout thread ids": "0",
"rollout DB malformed file names": "0",
"rollout DB missing active rows": "3123",
"rollout DB missing active sample": [
"/Users/davrig01/.codex/sessions/2026/05/01/rollout-2026-05-01T14-31-11-019de3bc-744e-7140-865d-6d15836121da.jsonl",
"/Users/davrig01/.codex/sessions/2026/05/01/rollout-2026-05-01T18-15-09-019de489-7fe9-7c60-8327-8b35818c8e28.jsonl",
"/Users/davrig01/.codex/sessions/2026/05/01/rollout-2026-05-01T14-36-15-019de3c1-16ce-75d0-a590-420256bbcaf2.jsonl",
"/Users/davrig01/.codex/sessions/2026/05/01/rollout-2026-05-01T12-48-07-019de35e-1816-7932-893b-84a9638d8f37.jsonl",
"/Users/davrig01/.codex/sessions/2026/05/01/rollout-2026-05-01T18-11-43-019de486-5b6b-7b30-8bbd-191c1cb4fe7e.jsonl"
],
"rollout DB missing archived rows": "0",
"rollout DB model providers": "proxy=1531, openai=4, =3",
"rollout DB rows": "1538",
"rollout DB scan cap reached": "false",
"rollout DB scan errors": "0",
"rollout DB sources": "exec=979, cli=294, vscode=194, subagent:review=40, subagent:thread_spawn=29, subagent:other=2",
"rollout DB stale rows": "0"
},
"issues": [
{
"severity": "warning",
"cause": "rollout files are missing from the state DB",
"measured": "3123 active, 0 archived",
"expected": "every rollout file has a matching threads row",
"remedy": null,
"fields": []
}
],
"remediation": null,
"durationMs": 1446
},
"system.disk": {
"id": "system.disk",
"category": "disk",
"status": "ok",
"summary": "sufficient free disk space (61.5 GiB)",
"details": {
"CODEX_HOME available": "61.5 GiB",
"failure threshold": "1.0 GiB",
"warning threshold": "5.0 GiB",
"worktree available": "61.5 GiB"
},
"remediation": null,
"durationMs": 0
},
"system.environment": {
"id": "system.environment",
"category": "system",
"status": "ok",
"summary": "OS language en-GB",
"details": {
"EDITOR": "set",
"LANG": "en_GB.UTF-8",
"VISUAL": "not set",
"os": "Mac OS 26.6.1 [64-bit]",
"os language": "en-GB",
"os type": "Mac OS",
"os version": "26.6.1"
},
"remediation": null,
"durationMs": 2
},
"terminal.env": {
"id": "terminal.env",
"category": "terminal",
"status": "ok",
"summary": "terminal metadata was detected",
"details": {
"CLICOLOR": "1",
"COLORTERM": "truecolor",
"DISPLAY": "present",
"TERM_PROGRAM": "Apple_Terminal",
"color output": "enabled",
"effective locale": "en_GB.UTF-8",
"stderr is terminal": "true",
"stdin is terminal": "true",
"stdout is terminal": "true",
"terminal": "Apple Terminal",
"terminal size": "151x70",
"terminal version": "470.2"
},
"remediation": null,
"durationMs": 0
},
"terminal.title": {
"id": "terminal.title",
"category": "title",
"status": "ok",
"summary": "terminal title default",
"details": {
"terminal title activity": "true",
"terminal title items": "activity, project-name",
"terminal title project source": "cwd",
"terminal title project value": "Downloads",
"terminal title source": "default"
},
"remediation": null,
"durationMs": 0
},
"updates.status": {
"id": "updates.status",
"category": "updates",
"status": "ok",
"summary": "update configuration is locally consistent",
"details": {
"cached latest version": "0.152.0",
"check for update on startup": "true",
"last checked at": "2026-09-01T08:04:30.042846Z",
"latest version": "0.152.1",
"latest version status": "current version is not older",
"update action": "manual or unknown",
"version cache": "/Users/davrig01/.codex/version.json"
},
"remediation": null,
"durationMs": 191
}
}
}
What issue are you seeing?
Recent versions of codex / models which use tool_mode=code_mode_only fail to correctly parse oneOf directives in the MCP inputSchema, resulting in such MCP tool calls failing validation.
An MCP tool input schema defines object properties beside a root-level oneOf. The oneOf branches specify which property is required:
{
"type": "object",
"properties": {
"field_a": { "type": "object" },
"field_n": { "type": "object" }
},
"oneOf": [
{ "required": ["field_a"] },
{ "required": ["field_n"] }
]
}
This pattern is used by an Arm Performix MCP tool to select one of several run modes. It worked in earlier Codex versions and still works with gpt-5.5.
When gpt-5.6-sol is used, Codex exposes this declaration to the model:
declare const tools: {
mcp__oneof_repro__test_tool(
args: { [key: string]: unknown } | { [key: string]: unknown }
): Promise<CallToolResult>;
};
Both property names and their types have been lost. The model therefore used an empty object for the first variant. The MCP server rejected it:
Input validation error: {} is not valid under any of the given schemas
The failure happens before the MCP tool can perform its operation.
What steps can reproduce the bug?
- Save the following file as
server.py:
#!/usr/bin/env -S uv run
# /// script
# requires-python = ">=3.10"
# dependencies = ["jsonschema==4.26.0", "mcp==2.1.1"]
# ///
"""Expose a tool with object properties beside a root-level `oneOf`."""
from __future__ import annotations
import asyncio
import json
import mcp.server.stdio
from jsonschema import ValidationError, validate
from mcp import types
from mcp.server.lowlevel import Server
INPUT_SCHEMA = {
"type": "object",
"properties": {
"field_a": {
"type": "object",
"description": "Select the first variant. Pass an empty object.",
"additionalProperties": False,
},
"field_n": {
"type": "object",
"description": "Select the second variant. Pass an empty object.",
"additionalProperties": False,
},
},
"oneOf": [
{"required": ["field_a"]},
{"required": ["field_n"]},
],
"additionalProperties": False,
}
async def list_tools(_context, _params) -> types.ListToolsResult:
return types.ListToolsResult(
tools=[
types.Tool(
name="test_tool",
description="Accept exactly one variant.",
inputSchema=INPUT_SCHEMA,
)
]
)
async def call_tool(_context, params: types.CallToolRequestParams) -> types.CallToolResult:
if params.name != "test_tool":
raise ValueError(f"unknown tool: {params.name}")
arguments = params.arguments or {}
try:
validate(arguments, INPUT_SCHEMA)
except ValidationError as error:
return types.CallToolResult(
content=[
types.TextContent(
type="text",
text=f"Input validation error: {error.message}",
)
],
isError=True,
)
return types.CallToolResult(
content=[
types.TextContent(
type="text",
text=json.dumps(
{"accepted_arguments": arguments},
sort_keys=True,
),
)
]
)
server = Server(
"codex-oneof-repro",
version="1.0.0",
on_list_tools=list_tools,
on_call_tool=call_tool,
)
async def main() -> None:
async with mcp.server.stdio.stdio_server() as (read, write):
await server.run(read, write, server.create_initialization_options())
if __name__ == "__main__":
asyncio.run(main())
- Add the server to
~/.codex/config.toml, replacing the path with the
absolute path toserver.py:
[mcp_servers.oneof_repro]
command = "uv"
args = ["run", "/absolute/path/to/server.py"]
default_tools_approval_mode = "approve"
- Run:
codex exec --ephemeral --skip-git-repo-check --model gpt-5.6-sol \
'Find test_tool in ALL_TOOLS and print its full description. Then call it exactly once with the first variant. Do not use shell commands. Report the tool result.'
What is the expected behavior?
Codex should preserve the sibling properties when it renders each oneOf
branch. The declaration should contain field_a and field_n, for example:
args:
| { field_a: {}; field_n?: {} }
| { field_a?: {}; field_n: {} }
The model can then make a valid call such as:
{"field_a": {}}
The server returns:
{"accepted_arguments": {"field_a": {}}}
Additional information
The regression boundary from repeated tests against the original MCP tool is:
| Codex CLI | Model/mode | Result |
|---|---|---|
| 0.144.0-alpha.3 | gpt-5.6-sol, previous tool mode | Pass |
| 0.144.0-alpha.4 | gpt-5.6-sol, code mode | Fail |
| 0.152.1 | gpt-5.6-sol, code mode | Fail |
| 0.152.1 | gpt-5.5 | Pass |
With gpt-5.5 on 0.152.1, Codex sent {"field_a": {}} and the server accepted it.
I suspect the likely cause is in codex-rs/code-mode-protocol/src/json_schema_types.rs.
JsonSchemaTypeRenderer::render_map renders anyOf and oneOf before it handles type and properties, and returns the rendered variants immediately.
Each variant in this reproducer contains only required, so each becomes { [key: string]: unknown }; the sibling property definitions are never included.
Related reports include #13768 and #20604, but they cover different schema shapes. This reproducer is specifically for object properties beside a root-level oneOf.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Reproduce the issue through non-interactive codex exec using an MCP schema with a root-level oneOf, then trace the tool_mode=code_mode_only schema-processing entry point. Done means sibling properties are retained in the resulting schema and the behavior has regression coverage.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- api, tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100