Skip to content

Routed Responses destinations 422 on Codex agent_message items replayed from sub-agent history #3911

Description

@mashfromband

Summary

Any Codex thread that has used sub-agents stops working as soon as it is routed to a
non-forward Responses destination. The destination rejects the whole request body with

422 {"error":"Failed to deserialize the JSON body into the target type: input[31]: unknown item type \"agent_message\"; expected one of: message, reasoning, function_call, function_call_output, shell_call, shell_call_output, web_search_call, file_search_call, code_interpreter_call, mcp_call, custom_tool_call, image_generation_call, compaction"}

agent_message is Codex's private multi-agent input item and exists only in the ChatGPT
Codex backend's schema. Codex writes every sub-agent reply into the rollout, so it is
replayed in the input of every later turn of that thread — the failure is not a
one-off, the thread is dead until the history is dropped.

422 is a client error, so nothing fails over: attempts has a single entry and the status
reaches the client verbatim.

Reproduction

Minimal, against a running ocx (the destination below is the built-in xai Responses
route; any non-forward Responses destination behaves the same):

curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:10100/v1/responses \
  -H 'content-type: application/json' -H 'authorization: Bearer local' \
  -d '{"model":"xai/grok-4.6","stream":false,"store":false,
       "instructions":"Answer with the single word OK.",
       "input":[
         {"type":"message","role":"user","content":[{"type":"input_text","text":"hello"}]},
         {"type":"agent_message","author":"/root/child","recipient":"/root",
          "content":[{"type":"input_text","text":"child reported: done"}]},
         {"type":"message","role":"user","content":[{"type":"input_text","text":"Say OK."}]}]}'
input[1] status
message (role user) 200
agent_message, plaintext content only 422
agent_message with encrypted_content 422

Replacing only input[1] with an equivalent message item makes the same request succeed,
so the item type alone is the trigger.

Observed in normal use

12 consecutive failures in one conversation over 15 minutes, every one of them:

provider: xai   model: grok-4.6   adapter: openai-responses   status: 422
routeKind: combo   requestedModel: auto   attempts: 1

The combo route had picked xai/grok-4.6 because the other candidates were on cooldown.
Nothing about the failure is specific to that provider — it is specific to the item type.

Current behaviour

src/adapters/openai-responses.ts converts the item only for the OpenCode Go destination:

if (!forward && isOpenCodeGo(provider.baseUrl)) outBody = normalizeOpenCodeGoAgentMessages(outBody);

and the docs state the scope explicitly
(docs-site/src/content/docs/reference/configuration/providers.md):

This conversion is scoped to that destination, including renamed provider entries;
other Responses destinations keep their input unchanged.

Keeping the item "unchanged" for other destinations is a guarantee of a 422: no third-party
Responses implementation accepts a private OpenAI item type.

Proposal

Apply the existing plaintext conversion to every routed (authMode !== "forward")
Responses destination, leaving the rest untouched:

  • forward destinations keep agent_message exactly as today — that is the only schema
    that understands it.
  • encrypted_content and unknown part types keep their current fail-closed path. The
    encrypted v2 task surface already owns those through unreadable_encrypted_agent_task
    and the opt-in recovery route, and this issue does not propose changing it.

The narrow scope looks like an artifact of the PR that introduced the helper (a Go-specific
carry) rather than a decision about other destinations; the conversion itself is already
lossless for plaintext content and preserves author/recipient as readable text.

PR to follow.

Activity

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

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions