Skip to content

[Bug]: 2026.4.26 injects unsupported metadata field into openai-codex Responses requests; ChatGPT backend returns HTTP 400, surfaced as "400 status code (no body)" #73963

@kasanuowa

Description

@kasanuowa

Bug type

Regression (worked before, now fails)

Beta release blocker

No

Summary

After upgrading to 2026.4.26, every request the openai-codex provider
sends to https://chatgpt.com/backend-api/codex/responses is rejected by
the ChatGPT codex backend with HTTP 400
{"detail":"Unsupported parameter: metadata"}. The error is surfaced to
the user as the misleading string 400 status code (no body) because the
SDK error parser expects an {"error":{"message":...}} shape and falls
through. All openai-codex/* models on the ChatGPT subscription path are
unusable until the provider stops attaching metadata to codex requests.

Steps to reproduce

  1. OpenClaw 2026.4.26, ChatGPT Plus OAuth profile for the
    openai-codex provider, baseUrl
    https://chatgpt.com/backend-api (default).

  2. Run any agent call against a codex model, e.g.

    openclaw agent --local --session-id repro-$$ \
      --message "say hi only" \
      --model openai-codex/gpt-5.4-mini --thinking low
    
  3. Observe the agent fail immediately with 400 status code (no body).

Expected behavior

The request succeeds. Sending the same payload manually (without the
metadata field) against the same endpoint with the same OAuth token
returns the assistant message normally — confirmed via curl. Earlier
OpenClaw releases (pre-resolveOpenAITransportTurnState) did not attach
this body field for codex and worked.

Actual behavior

The request body contains a top-level metadata object the codex backend
does not accept; backend responds with HTTP 400 and a 44-byte JSON body
{"detail":"Unsupported parameter: metadata"}. OpenClaw logs only show
error=400 status code (no body) rawError=400 status code (no body)
because the body is shaped {"detail":...}, not the
{"error":{"message":...}} form the SDK error parser expects.

OpenClaw version

2026.4.26 (be8c246)

Operating system

macOS (Darwin 25.4.0)

Install method

npm global

Model

openai-codex/gpt-5.4-mini (also reproduces on gpt-5.4, gpt-5.5)

Provider / routing chain

openclaw → openai-codex (ChatGPT subscription path,
https://chatgpt.com/backend-api/codex/responses) via ChatGPT Plus OAuth

Additional provider/model setup details

Default routing. models.providers.openai-codex.api is
openai-codex-responses and auth is oauth. The transport that fires
is createOpenAIResponsesTransportStreamFn in provider-stream-*.js (uses
the OpenAI SDK with client.responses.create, not the pi-ai
streamOpenAICodexResponses SSE path).

Logs, screenshots, and evidence

Captured via a temporary appendFile in fetch-guard-*.js around the
defaultFetch call, on the failing run:

=== REQ POST https://chatgpt.com/backend-api/codex/responses ===
BODY (parsed top-level keys):
  ['model', 'input', 'stream', 'prompt_cache_key', 'instructions',
   'metadata', 'tools', 'reasoning', 'include', 'store', 'text',
   'parallel_tool_calls']
  metadata: {
    "openclaw_session_id": "...",
    "openclaw_turn_id": "...",
    "openclaw_turn_attempt": "1",
    "openclaw_transport": "stream"
  }

RESP status=400
  content-length: 44
  content-type: application/json
  x-oai-request-id: a9cbd076-0b53-4b88-ba9c-ffa98cc2b98e
  body: {"detail":"Unsupported parameter: metadata"}

Sibling gateway-log evidence (~/.openclaw/logs/gateway.err.log) for
multiple runIDs:

[agent/embedded] embedded run agent end: runId=... isError=true
  model=gpt-5.4-mini provider=openai-codex
  error=400 status code (no body) rawError=400 status code (no body)

Each line is preceded by
[compaction-safeguard] Compaction safeguard: no real conversation messages to summarize… — secondary symptom from the failover loop, not
the root cause.

Impact and severity

  • Affected: every OpenClaw user on 2026.4.26 routing through
    openai-codex with a ChatGPT subscription auth profile (the most common
    setup for codex models).
  • Severity: High — codex models are completely unusable on this path.
    Fallback chains that include openai-codex/* also break, dragging
    down primary-model recovery.
  • Frequency: 100% reproducible on every codex request. Verified across
    gpt-5.4-mini, gpt-5.4, gpt-5.5 and across multiple OAuth profiles.
  • Consequence: agents fall through to other providers or surface the
    misleading "400 (no body)" error. The misleading message has caused
    follow-on debugging of OAuth, originator headers, proxy, and session
    state — none of which are the actual cause.

Additional information

Root cause. resolveOpenAITransportTurnState in
extensions/openai/transport-policy.ts returns a metadata block for
all OpenAI-family routes that pass usesKnownNativeOpenAIRoute, which
includes openai-codex against chatgpt.com/backend-api:

return {
  headers: { ...sessionHeaders, "x-openclaw-turn-id": turnId, ... },
  metadata: {
    openclaw_session_id: sessionHeaders["x-openclaw-session-id"] ?? "",
    openclaw_turn_id: turnId,
    openclaw_turn_attempt: attempt,
    openclaw_transport: ctx.transport
  }
};

The codex /backend-api/codex/responses endpoint validates a stricter
parameter allowlist than api.openai.com/v1/responses and does not
accept metadata. buildOpenAIResponsesParams then injects that
metadata into the request body for both endpoints
(...metadata ? { metadata } : {}), and mergeTransportMetadata merges
it again after onPayload. There is no payload-policy gate for
metadata (only store, service_tier, prompt_cache_*), so users
have no config-side workaround; #10140 had previously asked for exactly
such a configurable strip layer.

Suggested fix (smallest surface). In
resolveOpenAITransportTurnState, omit metadata when the provider is
openai-codex. Tracking via x-openclaw-* headers is preserved; the
codex backend ignores arbitrary x- headers. Drop-in patch verified
locally:

const isCodexProvider =
  normalizeProviderId(ctx.provider) === OPENAI_CODEX_PROVIDER_ID;
return {
  headers: {
    ...sessionHeaders,
    "x-openclaw-turn-id": turnId,
    "x-openclaw-turn-attempt": attempt,
  },
  ...isCodexProvider ? {} : {
    metadata: {
      openclaw_session_id: sessionHeaders["x-openclaw-session-id"] ?? "",
      openclaw_turn_id: turnId,
      openclaw_turn_attempt: attempt,
      openclaw_transport: ctx.transport,
    },
  },
};

Verified: same openclaw agent --local --message "say hi only" --model openai-codex/gpt-5.4-mini returns the assistant reply after
applying this patch in dist/transport-policy-*.js. Also confirmed the
manual curl with the unpatched body and metadata: {…} reproduces the
exact 400 with Unsupported parameter: metadata, while removing the
field returns a normal SSE stream.

Possible secondary improvement. The pi-ai error parser falling
through {"detail":...} bodies into "400 status code (no body)" hides
the actual upstream message and meaningfully delayed root-cause analysis.
A small fallback in parseErrorResponse that surfaces a top-level
detail string when no error.message is present would have made this
issue self-diagnosing.

Last known good / first known bad. Last known good: 2026.4.24
(transport-policy turn-state was added between 4.24 and 4.26 along with
the OpenAI-SDK transport switch for codex). First known bad: 2026.4.26.

Related issues

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions