fix(codex): tolerate response.output=None from ChatGPT Codex Responses stream - #32957
Closed
vitoropereira wants to merge 1 commit into
Closed
fix(codex): tolerate response.output=None from ChatGPT Codex Responses stream#32957vitoropereira wants to merge 1 commit into
vitoropereira wants to merge 1 commit into
Conversation
…s stream The openai SDK Responses accumulator raises "'NoneType' object is not iterable" when chatgpt.com/backend-api/codex emits an event whose response.output is None. run_codex_stream now routes that TypeError to the existing create(stream=True) fallback (narrow match so unrelated TypeErrors still propagate), and the fallback backfill treats output is None like an empty list. Adds a 4-test regression suite. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Collaborator
Contributor
|
Closing as duplicate — the Codex null-output fix has been merged via #32963 (cherry-picked from @carltonawong's PR #32890). Thanks for the help during the outage. Closes #11179. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Every turn on the
openai-codexChatGPT backend (https://chatgpt.com/backend-api/codex) currently aborts with'NoneType' object is not iterableand the user gets only an error fallback. This makes the Codex provider crash onget_final_response()/ stream accumulation routable to the existing manual-stream recovery path instead of a non-retryable client error, so the turn completes normally.Root cause
The OpenAI SDK's Responses accumulator (
openai/lib/_parsing/_responses.py:61) runsfor output in response.output:without guardingNone. The ChatGPT Codex backend emits a stream event whoseresponse.outputisNone(not[]), so the SDK raises:run_codex_streamalready routeshttpx.*transport errors and the SDK's prelude/postludeRuntimeErrorshapes to_run_codex_create_stream_fallback(which consumes the rawresponses.create(stream=True)stream manually and never calls the accumulator). The accumulator'sTypeErrorwas simply not in that catch list, so it escalated as a non-retryable error (conversation_looptreats it as a local validation / programming bug).This is not an auth/config/model problem: replaying the exact failing request body directly against the endpoint returns HTTP 200 with a complete, valid SSE stream. The failure is purely client-side stream parsing.
Fix
agent/codex_runtime.py:run_codex_stream— add aTypeErrorhandler alongside the existingRuntimeErrorone. The crash can fire both during stream iteration (accumulate_event) and atget_final_response(); catching it for the wholewith ... as stream:block covers both. The match is narrow (if "is not iterable" not in str(exc): raise) so genuineTypeErrors from our own stream callbacks still propagate — mirroring how theRuntimeErrorhandler re-raises unrelated shapes (locked bytest_codex_stream_unrelated_runtimeerror_still_raises).run_codex_create_stream_fallback— treat a terminaloutput is Nonelike an empty list, so the existing backfill (fromresponse.output_item.doneitems / text deltas) also covers theNonecase, not just[]:This matches Hermes' existing pattern of absorbing Codex backend stream quirks via the manual fallback, and fixes the crash without waiting on an upstream
openaiSDK release.How to Test
hermes auth add openai-codex(any ChatGPT-tier OAuth)hermes chat -q "say ok" --provider openai-codex -m gpt-5.5❌ Non-retryable client error: 'NoneType' object is not iterableagent.logshows a debug linefalling back to create(stream=True).Validation
New regression suite
tests/run_agent/test_codex_responses_output_none.py— 4/4 pass against the realAIAgent+ real Codex preflight:No regression in the existing codex/streaming suites:
Not yet verified end-to-end: that the live
chatgpt.com/backend-api/codexbackend's manualcreate(stream=True)terminal event carries the real, complete output. The unit tests fake the stream events; the in-place control flow is proven, but a live confirmation against the backend is the remaining gap.Type of Change
Related issues & prior art
This crash is tracked by several issues — closest is #32908 (notes the existing backfill at
codex_runtime.pyis bypassed because the accumulator raises before it), plus #32892, #32894, #32903, #11179.There are also open PRs targeting the same crash (e.g. #32919, #32939, #32921, #32890, #32884). This PR's angle: a narrowly-matched
TypeErrorhandler that reuses the existingcreate(stream=True)fallback to cover both crash sites (mid-iteration andget_final_response()), plus theoutput is Noneguard in the fallback backfill, with a focused regression suite. Happy to consolidate with whichever approach maintainers prefer.🤖 Generated with Claude Code