Skip to content

fix(codex_responses): recover from SDK parse_response TypeError when output is None - #33083

Closed
domnul-baz wants to merge 2 commits into
NousResearch:mainfrom
domnul-baz:fix/codex-stream-typeerror-output-none
Closed

fix(codex_responses): recover from SDK parse_response TypeError when output is None#33083
domnul-baz wants to merge 2 commits into
NousResearch:mainfrom
domnul-baz:fix/codex-stream-typeerror-output-none

Conversation

@domnul-baz

Copy link
Copy Markdown

Summary

Fixes a runtime crash where the openai-codex provider raises TypeError: 'NoneType' object is not iterable mid-stream when the ChatGPT Codex backend emits a response.completed event with output=None (not []).

Refs #5678.

Root cause

OpenAI SDK's parse_response (openai/lib/_parsing/_responses.py:61) iterates response.output without a None guard:

for output in response.output:
    ...

When the backend at chatgpt.com/backend-api/codex emits response.completed with output=None, this crashes inside accumulate_event before Hermes' existing empty-output workarounds (run_codex_stream lines 248-266) can fire — those only handle output == [] after get_final_response(), which never returns because the SDK raises first.

Full traceback (captured from gpt-5.5 Discord conversation):

File "agent/codex_runtime.py", line 196, in run_codex_stream
    for event in stream:
File "openai/lib/streaming/responses/_responses.py", line 248, in handle_event
    self.__current_snapshot = snapshot = self.accumulate_event(event)
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

Fix

Catch TypeError in the stream loop. If we have accumulated text deltas and aren't in a tool-call stream, synthesize a SimpleNamespace response from the deltas — mirrors the existing pattern at lines 256-262. If recovery is unsafe (tool calls, or zero deltas), fall back to _run_codex_create_stream_fallback so the outer retry loop has something useful to work with.

Test plan

  • Reproduce on gpt-5.5 via Discord gateway with openai-codex provider — confirmed NoneType traceback before patch.
  • Apply patch, restart gateway, retry same conversation — confirmed clean response and single WARNING agent.codex_runtime: Codex stream parse_response TypeError (SDK bug on output=None) — recovered by synthesizing response from N streamed deltas (M chars) log line.
  • Repro on gpt-5.3-codex, gpt-5.4-mini — same provider, expected same fix coverage.
  • Tool-call stream coverage — when has_tool_calls=True, the patch falls through to _run_codex_create_stream_fallback (existing path). Worth a deliberate test if a reviewer can craft one.

Notes

  • The title_generator aux client appears to hit the same SDK bug on a different code path — out of scope here, but worth a follow-up patch with the same recovery pattern in the auxiliary client wrapper.
  • Local Hermes harness was running into this on every conversation on the ChatGPT Codex backend; this patch unblocks it.

🤖 Generated with Claude Code

Baz added 2 commits May 27, 2026 07:12
…output is None

The OpenAI SDK's parse_response (openai/lib/_parsing/_responses.py) iterates
`response.output` without a None guard. When the ChatGPT Codex backend
(chatgpt.com/backend-api/codex) emits a `response.completed` event whose
`output` field is None (not []), the SDK raises mid-stream:

    TypeError: 'NoneType' object is not iterable

This crashes inside `accumulate_event` before our existing empty-output
workarounds at run_codex_stream() lines 248-266 can fire — those handle
`output == []` after `get_final_response()`, but the SDK never gets that
far when `output is None`.

Reproducer: any Discord/CLI conversation through the openai-codex
provider on gpt-5.5 or gpt-5.3-codex with non-trivial context. The
streamed `output_text.delta` events deliver valid text, but parse_response
chokes when assembling the final response object.

Recovery: catch TypeError in the stream loop. If we have accumulated
text deltas and aren't in a tool-call stream (where structured args
matter more than text), synthesize a SimpleNamespace response from the
deltas — mirrors the existing pattern at lines 256-262 used when
`output == []`. If recovery is unsafe (tool calls or zero deltas), fall
back to `_run_codex_create_stream_fallback` so the outer retry loop has
something useful to work with.

Refs NousResearch#5678. Validated against gpt-5.5 with chatgpt.com Codex OAuth
backend: failing conversation now returns coherent assistant text with
a single WARNING log line ("recovered by synthesizing response from N
streamed deltas").
First pass only synthesized text-only messages from `_codex_streamed_text_parts`.
That covered short chat turns but failed on tool-using turns: when the agent
delegates work (`response.output_item.done` events carrying `function_call`
items), the text-delta buffer is often empty and `has_tool_calls=True` —
the previous patch then fell through to `_run_codex_create_stream_fallback`,
which re-hit the same SDK bug and never recovered.

Extend recovery to a 3-tier ladder:

  1. ``collected_output_items`` — the canonical structured items the backend
     already emitted via ``response.output_item.done``. Preserves both
     assistant messages and function_call argument shape, so tool-calling
     turns recover correctly.
  2. Text-only delta synthesis — unchanged, used when no structured items
     arrived but text deltas did.
  3. ``_run_codex_create_stream_fallback`` — last resort, unchanged.

Reproducer: ask the agent any question that triggers a tool call (memory
lookup, session_search, delegation). Pre-fix: same NoneType crash, no
recovery log. Post-fix: WARNING "recovered from N collected output items
(tool_calls=True)" and a coherent assistant turn.
@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 provider/copilot GitHub Copilot (ACP + Chat) duplicate This issue or pull request already exists labels May 27, 2026
@domnul-baz

Copy link
Copy Markdown
Author

Closing as redundant — origin/main already has a more comprehensive fix for the same root cause (_responses_null_output_iterable_error + _codex_backfilled_response + the _codex_stream_last_event_ts watchdog), apparently merged shortly before I cut this branch. My local clone was on a stale feature branch and I missed the merged work when crafting the PR.

Updated locally by pulling agent/codex_runtime.py from origin/main and verifying recovery on the same gpt-5.5 Discord reproducer. Thanks for already shipping the fix.

@domnul-baz domnul-baz closed this May 27, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Duplicate of #32963 (merged) which already landed the same TypeError guard for Codex null-output streams in run_codex_stream(). The merged fix adds except TypeError handling in both the main stream path and the fallback path via _responses_null_output_iterable_error() + _responses_backfilled_response() helpers. This PR is a late-arriving fix for an already-resolved crash. See also #33050 (same late duplicate).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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/copilot GitHub Copilot (ACP + Chat) type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants