fix(codex): raise RuntimeError when response.output is None (Cloudflare/non-SSE body) - #34642
Conversation
…re/non-SSE body)
When the ChatGPT Codex backend serves a Cloudflare challenge page or any
non-SSE HTML body, the OpenAI SDK's stream parser silently leaves
response.output as None rather than raising an exception. This None
propagates to _normalize_codex_response which iterates output, causing
a TypeError: 'NoneType' object is not iterable.
The conversation loop classifies TypeError as is_local_validation_error
(a programming bug, non-retryable) and immediately triggers the fallback
chain. While the fallback itself works correctly, the error message is
misleading ('NoneType object is not iterable') and the TypeError
classification wastes a retry slot.
Fix: add a guard in run_codex_stream after get_final_response() that
detects output=None, includes any streamed text as a diagnostic hint in
the message (useful for identifying Cloudflare pages in logs), and raises
RuntimeError instead. RuntimeError is classified as retryable by the
conversation loop, which gives the retry logic a chance to recover before
falling back.
Root cause confirmed via agent.log: the Codex endpoint
chatgpt.com/backend-api/codex was serving a Cloudflare JS challenge page
when Hermes exceeded the per-IP request rate from the local gateway.
Reproducer: any session that exhausts the Cloudflare challenge threshold
on the Codex backend will manifest as a TypeError in the conversation
loop with summary="'NoneType' object is not iterable".
Fixes NousResearch#31093
|
Superseded by merged #32963 — the canonical Codex null-output fix. The current |
|
Thanks for this — closing as already fixed on The Codex null-output crash (
Your fix targets the same path that's now hardened upstream, so there's nothing left to merge here. Your authorship is preserved in your branch; I'm crediting your report in the umbrella issue #33932 (now closed). Appreciate the contribution. |
Summary
When the ChatGPT Codex backend serves a Cloudflare challenge page or any non-SSE HTML body (gateway error, temporary block), the OpenAI SDK's stream parser silently leaves
response.outputasNonerather than raising an exception.This
Nonepropagates to_normalize_codex_responsewhich tries to iterateoutput, resulting in:The conversation loop classifies
TypeErrorasis_local_validation_error(a programming bug, non-retryable) and immediately triggers the fallback chain. The error message surfaces to users as the cryptic'NoneType' object is not iterablewith no indication of the real cause.Root Cause
Confirmed via
agent.log: the Codex endpointchatgpt.com/backend-api/codexwas serving a Cloudflare JS challenge page when Hermes exceeded the per-IP request rate from the local gateway. The Cloudflare page was being silently swallowed by the SDK's stream parser and exposed downstream asoutput=None.Fix
Add a guard in
run_codex_streamimmediately afterstream.get_final_response()that:output is None(distinct fromoutput == []which the existing PATCH already handles)RuntimeErrorinstead ofTypeErrorRuntimeErroris classified as retryable by the conversation loop, giving the retry mechanism a chance to recover before triggering the fallback.Before / After
Before:
TypeError: 'NoneType' object is not iterable— classified as non-retryable local bug, misleading, immediately falls back.After:
RuntimeError: Codex Responses stream completed but response.output is None — the backend may have returned a non-SSE body (Cloudflare challenge, gateway error page, or empty response). Streamed text before failure: '<html>...— classified as retryable, descriptive, includes diagnostic hint.Test
Related: #21444 (gpt-5.5 silent reject pattern on chatgpt.com backend)