fix: harden ChatGPT Responses missing content-type streams#90487
Conversation
Send SSE Accept headers for native ChatGPT/Codex Responses stream requests, including the transport-aware alias path. Only normalize missing Content-Type responses for the native HTTPS ChatGPT/Codex backend after the body sniffs as SSE. Keep JSON, HTML, unknown bodies, and non-native providers fail-closed.
|
Codex review: needs maintainer review before merge. Reviewed June 5, 2026, 6:03 AM ET / 10:03 UTC. Summary PR surface: Source +45, Tests +124. Total +169 across 4 files. Reproducibility: yes. The linked report gives concrete failing Review metrics: 1 noteworthy metric.
Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Land this shape only after maintainers explicitly accept the stricter missing-header compatibility boundary; otherwise keep native SSE support and choose a narrower owner-approved compatibility path. Do we have a high-confidence way to reproduce the issue? Yes. The linked report gives concrete failing Is this the best way to solve the issue? Yes, with a maintainer-policy caveat. The request-options and guarded-fetch boundary is the right layer for this SDK stream issue, but maintainers still need to accept the stricter compatibility policy before merge. AGENTS.md: found and applied where relevant. Codex review notes: model gpt-5.5, reasoning high; reviewed against 1a3ce7c2a8da. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +45, Tests +124. Total +169 across 4 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. |
|
@clawsweeper re-review |
|
🦞👀 Command router queued. I will update this comment with the next step. Re-review progress:
|
|
Adding a macOS repro that appears to match the Environment:
Failing case: openclaw infer model run --gateway --model openai/gpt-5.5 --prompt 'Reply with exactly: OPENCLAW-55-OK' --jsonResult: Earlier probe/gateway logs for the same failure showed: The failed session resolves as: Control case: On the same host, same OAuth account, and same canonical So this does not appear to be expired OAuth, gateway offline, bad model ref, proxy/baseUrl override, or generic OpenAI OAuth failure. It looks specific to: I also tested provider-scoped runtime policy: "models": {
"providers": {
"openai": {
"agentRuntime": { "id": "openclaw" }
}
}
}
This looks aligned with the fix in PR #90205, where missing |
Summary
Builds on #90399 and narrows the missing-
Content-Typeworkaround to the native ChatGPT/Codex Responses stream path.Accept: text/event-streamfor native ChatGPT/Codex Responses SDK stream requests, including the transport-aware alias pathtext/event-streamwhen the response is2xx, has a body, targets the expectedchatgpt.com/backend-apior/codexbackend, and the body prefix sniffs as SSEWhy
#90399 covers the direct
openai-chatgpt-responsesAPI value, but the transport-aware stream path can route throughopenclaw-openai-responses-transport. Matching only the direct API value leaves that path without the proactive SSEAcceptheader.The response-side guard also should not treat every missing
Content-Typeas acceptable. A missing header is only safe to normalize for the native ChatGPT/Codex Responses backend after the body confirms it is SSE.Related: #90083. Current
mainalready contains the central body-sniffing normalization for this missing-Content-TypeSSE failure mode; this PR keeps the native ChatGPT/Codex SSE path working while tightening the missing-header normalization boundary and adding the scoped SSEAcceptheader. It does not claim coverage for unrelated payload-shape/provider failures.Tests
pnpm exec vitest run --config test/vitest/vitest.agents.config.ts src/agents/provider-transport-fetch.test.ts src/agents/openai-transport-stream.test.tspnpm exec oxlint src/agents/openai-transport-stream.ts src/agents/openai-transport-stream.test.ts src/agents/provider-transport-fetch.ts src/agents/provider-transport-fetch.test.tspnpm tsgo:corepnpm exec tsgo -p test/tsconfig/tsconfig.core.test.agents.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/core-test-agents.tsbuildinfopnpm exec oxfmt --check src/agents/openai-transport-stream.ts src/agents/openai-transport-stream.test.ts src/agents/provider-transport-fetch.ts src/agents/provider-transport-fetch.test.tsReal behavior proof
Behavior addressed: Native ChatGPT/Codex Responses streaming through the transport-aware alias should send
Accept: text/event-streamon the SDK stream request. When that native path receives an SSE body with a missingContent-Type, the SDK stream should still parse events successfully. Missing-header bodies that do not sniff as SSE should fail closed; this is intentional for non-SSE/native responses and for non-native provider routes.Real setup tested: Source checkout at PR commit
90cbbeebc449using this branch's actual OpenAI SDK path,buildGuardedModelFetch, andbuildOpenAISdkRequestOptionsoutput. Non-production local harness: a loopback CONNECT proxy routedhttps://chatgpt.com/backend-api/codex/responsesto a local TLS server with a generatedchatgpt.comcertificate. This kept traffic off external endpoints and used no real provider credentials. The local harness disabled TLS verification only for the generated local certificate; this proof is about request headers, missing-header SSE normalization, and fail-closed body classification, not TLS trust behavior. Model route under test:provider=openai,api=openclaw-openai-responses-transport,baseUrl=https://chatgpt.com/backend-api/codex.Exact steps or command run after this patch: Ran a temporary proof harness with
node --import tsxfrom this PR branch. The harness generated a local certificate forchatgpt.com, started a loopback CONNECT proxy and local TLS server, created an OpenAI SDK client using this branch's guarded fetch, passed this branch's stream request options intoclient.responses.create({ stream: true }), and repeated the request with a non-SSE missing-header body to verify fail-closed behavior.Evidence after fix: copied terminal output from the non-production proof harness:
Observed result after fix: The transport-aware alias path sent
Accept: text/event-streamon the actual SDK request. The SDK parsed a missing-Content-TypeSSE response successfully after guarded-fetch normalization. A missing-Content-Typenon-SSE body was rejected throughProviderHttpErrorwithinvalid_provider_content_type, confirming the stricter fail-closed behavior is intentional.What was not tested: No live ChatGPT OAuth/provider endpoint was called. No production Gateway/runtime state, user session, or real credential was used. This proof does not claim coverage for arbitrary OpenAI-compatible providers; those routes intentionally remain fail-closed for missing-header non-native streams.