Skip to content

fix(codex): tolerate response.output=None from ChatGPT Codex Responses stream - #32957

Closed
vitoropereira wants to merge 1 commit into
NousResearch:mainfrom
vitoropereira:fix/codex-output-none
Closed

fix(codex): tolerate response.output=None from ChatGPT Codex Responses stream#32957
vitoropereira wants to merge 1 commit into
NousResearch:mainfrom
vitoropereira:fix/codex-output-none

Conversation

@vitoropereira

Copy link
Copy Markdown

Summary

Every turn on the openai-codex ChatGPT backend (https://chatgpt.com/backend-api/codex) currently aborts with 'NoneType' object is not iterable and the user gets only an error fallback. This makes the Codex provider crash on get_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) runs for output in response.output: without guarding None. The ChatGPT Codex backend emits a stream event whose response.output is None (not []), so the SDK raises:

File ".../openai/lib/streaming/responses/_responses.py", line 360, in accumulate_event
    self._completed_response = parse_response(...)
File ".../openai/lib/_parsing/_responses.py", line 61, in parse_response
    for output in response.output:
TypeError: 'NoneType' object is not iterable

run_codex_stream already routes httpx.* transport errors and the SDK's prelude/postlude RuntimeError shapes to _run_codex_create_stream_fallback (which consumes the raw responses.create(stream=True) stream manually and never calls the accumulator). The accumulator's TypeError was simply not in that catch list, so it escalated as a non-retryable error (conversation_loop treats 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:

  1. run_codex_stream — add a TypeError handler alongside the existing RuntimeError one. The crash can fire both during stream iteration (accumulate_event) and at get_final_response(); catching it for the whole with ... as stream: block covers both. The match is narrow (if "is not iterable" not in str(exc): raise) so genuine TypeErrors from our own stream callbacks still propagate — mirroring how the RuntimeError handler re-raises unrelated shapes (locked by test_codex_stream_unrelated_runtimeerror_still_raises).

    except TypeError as exc:
        if "is not iterable" not in str(exc):
            raise
        logger.debug(
            "Responses stream accumulator raised TypeError (%s); "
            "falling back to create(stream=True). %s",
            exc, agent._client_log_context(),
        )
        return agent._run_codex_create_stream_fallback(api_kwargs, client=active_client)
  2. run_codex_create_stream_fallback — treat a terminal output is None like an empty list, so the existing backfill (from response.output_item.done items / text deltas) also covers the None case, not just []:

    - _out = getattr(terminal_response, "output", None)
    - if isinstance(_out, list) and not _out:
    + _out = getattr(terminal_response, "output", None)
    + if _out is None or (isinstance(_out, list) and not _out):

This matches Hermes' existing pattern of absorbing Codex backend stream quirks via the manual fallback, and fixes the crash without waiting on an upstream openai SDK release.

How to Test

  1. hermes auth add openai-codex (any ChatGPT-tier OAuth)
  2. hermes chat -q "say ok" --provider openai-codex -m gpt-5.5
  3. Before: ❌ Non-retryable client error: 'NoneType' object is not iterable
  4. After: the model's response is returned; agent.log shows a debug line falling back to create(stream=True).

Validation

New regression suite tests/run_agent/test_codex_responses_output_none.py — 4/4 pass against the real AIAgent + real Codex preflight:

test_codex_stream_accumulator_typeerror_falls_back_to_create_stream  PASSED
test_codex_stream_unrelated_typeerror_still_raises                   PASSED
test_fallback_synthesizes_output_when_terminal_output_is_none        PASSED
test_fallback_backfills_items_when_terminal_output_is_none           PASSED

No regression in the existing codex/streaming suites:

tests/run_agent/test_codex_xai_oauth_recovery.py
tests/run_agent/test_run_agent_codex_responses.py
tests/run_agent/test_streaming.py
                                            142 passed in 41.79s

Not yet verified end-to-end: that the live chatgpt.com/backend-api/codex backend's manual create(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

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✅ Tests (adding or improving test coverage)

Related issues & prior art

This crash is tracked by several issues — closest is #32908 (notes the existing backfill at codex_runtime.py is 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 TypeError handler that reuses the existing create(stream=True) fallback to cover both crash sites (mid-iteration and get_final_response()), plus the output is None guard in the fallback backfill, with a focused regression suite. Happy to consolidate with whichever approach maintainers prefer.

🤖 Generated with Claude Code

…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>
@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint codex provider/openai OpenAI / Codex Responses API duplicate This issue or pull request already exists labels May 27, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Duplicate of #32884 — same Codex null output recovery approach (catch TypeError, fallback to manual stream). Root cause: #11179.

@teknium1

Copy link
Copy Markdown
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.

@teknium1 teknium1 closed this May 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

codex comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint duplicate This issue or pull request already exists P3 Low — cosmetic, nice to have provider/openai OpenAI / Codex Responses API type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants