Skip to content

[Bug] openai-chat V2 spawn_agent emits empty arguments when message schema contains encrypted:true #1774

Closed

Description

@ZHJay

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: 0

Upstream 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

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

  1. coderabbitai commented on Aug 15, 2026

    @coderabbitai
    Contributor
    🔗 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 plan command to generate an implementation plan and prompts that you can use with your favorite coding assistant.

    • Create Plan

    🧪 Issue enrichment is currently in open beta.

    To disable automatic issue enrichment, add the following to your .coderabbit.yaml:

    issue_enrichment:
      auto_enrich:
        enabled: false

    💬 Have feedback or questions? Drop into our discord!

  2. added a commit that references this issue on Aug 15, 2026
    02728d2
  3. lidge-jun commented on Aug 15, 2026

    @lidge-jun
    Owner

    Fixed on dev as 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 encrypted survive, literal payloads under const/default/enum/examples are 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.

  4. added a commit that references this issue on Sep 17, 2026
    988d3c0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    providerProvider adapters, OpenAI-compat presets, upstream API quirksprovider-compatibilityProvider compatibility reports

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions