Repository navigation
[Bug] openai-chat V2 spawn_agent emits empty arguments when message schema contains encrypted:true #1774
Description
Client or integration
Codex App
Provider or upstream service
burnopenai (OpenAI-compatible Chat Completions upstream through the openai-chat adapter)
OpenCodex version
2.19.0 (also present by static inspection on current dev at c71c827)
Endpoint or capability
/v1/responses to openai-chat / Chat Completions translation; V2 collaboration spawn_agent tool calls
Current behaviour
With V2 collaboration enabled and a routed openai-chat parent, Codex supplies a complete spawn_agent schema whose required message property carries the Responses-only annotation encrypted: true.
OpenCodex preserves required: ["message"], but also forwards the non-standard annotation unchanged in tools[].function.parameters to the Chat Completions upstream. In the reproduced provider/model combination, the upstream then emits a spawn_agent call with zero argument bytes; OpenCodex normalizes that to {}, and Codex rejects it because message is missing. No child agent is created, and the parent retries.
One archived session produced 24 spawn_agent calls: 24/24 had arguments exactly {}, and all 24 corresponding tool results reported missing field message.
The generic openai-chat path still forwards the annotation on current dev; only the Zen-specific schema sanitizer currently drops it.
Expected behaviour
At the Responses to Chat Completions adapter boundary, OpenCodex should remove the Responses-only encrypted schema annotation before serializing Chat Completions tools, while preserving:
- required: ["message"] and all other supported schema constraints;
- legitimate tool property/definition names that are literally encrypted;
- literal data under keywords such as const, default, enum, and examples.
Native OpenAI Responses passthrough should continue to preserve the annotation. The routed parent should emit a valid, non-empty message and create the child once.
Minimal redacted request or reproduction
# Environment
# OpenCodex 2.19.0, macOS, V2 collaboration
# Parent: burnopenai/gpt-5.6-sol, reasoning effort xhigh
# 1. Start a fresh Codex App task using the routed parent.
# 2. Ask it to delegate exactly one self-contained task with spawn_agent.
# 3. Observe repeated empty spawn_agent calls and no child creation.
# The incoming schema at the adapter boundary contains:
{
"type": "object",
"properties": {
"message": { "type": "string", "encrypted": true }
},
"required": ["message"]
}
# On unmodified 2.19.0, createOpenAIChatAdapter(...).buildRequest(...)
# serializes the same message schema to tools[].function.parameters,
# including encrypted:true.A controlled forced-tool A/B used the same upstream, model, prompt, and spawn_agent tool:
message schema contains encrypted:true -> arguments are empty ("" / {})
only the encrypted annotation removed -> {"message":"Reply exactly: READY"}
The result was the same for flat and namespace: collaboration tool shapes, which rules out namespace conversion as the differentiator.
Actual response or error
function_call name=spawn_agent namespace=collaboration arguments={}
function_call_output failed to parse function arguments: missing field message at line 1 column 2
# Redacted session totals:
spawn_agent calls: 24
arguments exactly {}: 24
missing-field-message errors: 24
children created: 0Upstream documentation
No public provider or JSON Schema specification was found for the Codex encrypted annotation. It is identified in this repository as a Responses/ChatGPT-backend-only marker introduced by upstream OpenAI Codex, and the same compatibility class was confirmed and fixed for Google/Kiro in #85 and defensively handled by the Anthropic adapter.
The concrete client requirement is that Responses-private schema metadata must not change required Chat Completions tool-call arguments during protocol translation.
Suggested mapping or implementation notes
Sanitize tool parameter schemas once in the generic openai-chat adapter before provider-specific Chat Completions formatting. Strip encrypted only when it is a schema keyword, not when it is a property/definition name or literal JSON data. The existing Anthropic implementation already distinguishes schema-name maps and literal-value keywords and may be a useful precedent.
A focused regression should assert that nested annotations are removed, required: ["message"] survives, a property literally named encrypted survives, literal encrypted data survives, and the input schema is not mutated.
I am happy to prepare a focused PR with the adapter fix and behavior-contract regression test.
Additional context and attachments
- Gemini side task fails with 400 due to unsupported encrypted field #85 confirms the same Responses-only annotation class for other adapters.
- V2 custom-model parent emits empty spawn_agent arguments for custom-model children #290 reports the same empty-argument symptom, but was closed because the available trace only established that the provider returned zero argument bytes; it did not isolate why.
- [Bug] V2 custom-parent → custom-child delegation needs current-dev trace #418 was later closed with an explicit invitation to open a new issue if the newest version still reproduced with a current trace.
- This is not V2 cross-provider sub-agent loses NEW_TASK body in encrypted_content #92: V2 cross-provider sub-agent loses NEW_TASK body in encrypted_content #92 fails after a valid spawn when a routed child cannot read an encrypted NEW_TASK; this issue fails before child creation because spawn_agent.message is absent.
- The archived rollout, configuration, credentials, full prompt, and provider headers are intentionally not attached. The counts and wire shapes above are redacted.
- The affected Codex App session recorded cli_version: 0.148.0-alpha.9; opencodex status reports the configured local Codex CLI as 0.145.0.
Checks
- I searched existing provider and compatibility issues.
- The request and response were redacted.
- The expected behaviour is based on an upstream specification or a concrete client requirement.
Activity
coderabbitai commentedon Aug 15, 2026
🔗 Related PRs
#302 - feat(kiro): harden completion and transport integration [merged]
#492 - fix(openai-responses): handle image tool conflicts in additional_tools [merged]
#1479 - fix(deepseek): preserve parallel reasoning replay [open]
#1499 - fix(deepseek): keep parallel reasoning replay on continuation [open]
#1531 - fix(openai-chat): refuse to emit an unnamed streamed tool call [merged]
📝 Issue Planner
Check the box below or use the
@coderabbitai plancommand to generate an implementation plan and prompts that you can use with your favorite coding assistant.🧪 Issue enrichment is currently in open beta.
To disable automatic issue enrichment, add the following to your
.coderabbit.yaml:💬 Have feedback or questions? Drop into our discord!
fix(openai-chat): strip the Responses-only encrypted tool annotation (#…
lidge-jun commentedon Aug 15, 2026
Fixed on
devas 02728d2 (PR #1779, carrying @ZHJay's #1776).The marker is stripped at the protocol boundary rather than by provider name, sharing one implementation with the Anthropic path. Properties and definitions literally named
encryptedsurvive, literal payloads underconst/default/enum/examplesare untouched, the input schema is not mutated, and native Responses passthrough keeps the annotation.One change beyond the original patch: the walk is iterative over an explicit stack. Tool schemas are caller-supplied, so nesting depth is attacker-influenced, and the recursive version would have answered a deep schema with a stack overflow on the request path. A 50,000-deep regression covers it.
Verified on the remote validation host: 129 pass / 0 fail across
tests/openai-chat-hardening.test.ts,tests/anthropic-tool-schema.test.ts,tests/openai-responses-passthrough.test.ts.fix(openai-chat): strip the Responses-only encrypted tool annotation (l…