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
-
OpenClaw 2026.4.26, ChatGPT Plus OAuth profile for the
openai-codex provider, baseUrl
https://chatgpt.com/backend-api (default).
-
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
-
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
Bug type
Regression (worked before, now fails)
Beta release blocker
No
Summary
After upgrading to 2026.4.26, every request the
openai-codexprovidersends to
https://chatgpt.com/backend-api/codex/responsesis rejected bythe ChatGPT codex backend with HTTP 400
{"detail":"Unsupported parameter: metadata"}. The error is surfaced tothe user as the misleading string
400 status code (no body)because theSDK error parser expects an
{"error":{"message":...}}shape and fallsthrough. All
openai-codex/*models on the ChatGPT subscription path areunusable until the provider stops attaching
metadatato codex requests.Steps to reproduce
OpenClaw 2026.4.26, ChatGPT Plus OAuth profile for the
openai-codexprovider, baseUrlhttps://chatgpt.com/backend-api(default).Run any agent call against a codex model, e.g.
Observe the agent fail immediately with
400 status code (no body).Expected behavior
The request succeeds. Sending the same payload manually (without the
metadatafield) against the same endpoint with the same OAuth tokenreturns the assistant message normally — confirmed via curl. Earlier
OpenClaw releases (pre-
resolveOpenAITransportTurnState) did not attachthis body field for codex and worked.
Actual behavior
The request body contains a top-level
metadataobject the codex backenddoes not accept; backend responds with HTTP 400 and a 44-byte JSON body
{"detail":"Unsupported parameter: metadata"}. OpenClaw logs only showerror=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 ongpt-5.4,gpt-5.5)Provider / routing chain
openclaw → openai-codex (ChatGPT subscription path,
https://chatgpt.com/backend-api/codex/responses) via ChatGPT Plus OAuthAdditional provider/model setup details
Default routing.
models.providers.openai-codex.apiisopenai-codex-responsesandauthisoauth. The transport that firesis
createOpenAIResponsesTransportStreamFninprovider-stream-*.js(usesthe OpenAI SDK with
client.responses.create, not the pi-aistreamOpenAICodexResponsesSSE path).Logs, screenshots, and evidence
Captured via a temporary
appendFileinfetch-guard-*.jsaround thedefaultFetchcall, on the failing run:Sibling gateway-log evidence (
~/.openclaw/logs/gateway.err.log) formultiple runIDs:
Each line is preceded by
[compaction-safeguard] Compaction safeguard: no real conversation messages to summarize…— secondary symptom from the failover loop, notthe root cause.
Impact and severity
openai-codexwith a ChatGPT subscription auth profile (the most commonsetup for codex models).
Fallback chains that include
openai-codex/*also break, draggingdown primary-model recovery.
gpt-5.4-mini,gpt-5.4,gpt-5.5and across multiple OAuth profiles.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.
resolveOpenAITransportTurnStateinextensions/openai/transport-policy.tsreturns ametadatablock forall OpenAI-family routes that pass
usesKnownNativeOpenAIRoute, whichincludes
openai-codexagainstchatgpt.com/backend-api:The codex
/backend-api/codex/responsesendpoint validates a stricterparameter allowlist than
api.openai.com/v1/responsesand does notaccept
metadata.buildOpenAIResponsesParamsthen injects thatmetadatainto the request body for both endpoints(
...metadata ? { metadata } : {}), andmergeTransportMetadatamergesit again after
onPayload. There is no payload-policy gate formetadata(onlystore,service_tier,prompt_cache_*), so usershave no config-side workaround; #10140 had previously asked for exactly
such a configurable strip layer.
Suggested fix (smallest surface). In
resolveOpenAITransportTurnState, omitmetadatawhen the provider isopenai-codex. Tracking viax-openclaw-*headers is preserved; thecodex backend ignores arbitrary
x-headers. Drop-in patch verifiedlocally:
Verified: same
openclaw agent --local --message "say hi only" --model openai-codex/gpt-5.4-minireturns the assistant reply afterapplying this patch in
dist/transport-policy-*.js. Also confirmed themanual curl with the unpatched body and
metadata: {…}reproduces theexact 400 with
Unsupported parameter: metadata, while removing thefield returns a normal SSE stream.
Possible secondary improvement. The pi-ai error parser falling
through
{"detail":...}bodies into "400 status code (no body)" hidesthe actual upstream message and meaningfully delayed root-cause analysis.
A small fallback in
parseErrorResponsethat surfaces a top-leveldetailstring when noerror.messageis present would have made thisissue 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
drop-unsupported-fields layer; would have prevented this if landed.
google-generative-aiOpenAI-compatendpoint (rejected
metadata/store/service_tier); fix wasscoped to
google-generative-aionly and did not extend to codex.input[],not the same root cause but compounding the diagnostic difficulty.