fix(codex_responses): recover from SDK parse_response TypeError when output is None - #33083
fix(codex_responses): recover from SDK parse_response TypeError when output is None#33083domnul-baz wants to merge 2 commits into
Conversation
…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.
|
Closing as redundant — Updated locally by pulling |
|
Duplicate of #32963 (merged) which already landed the same TypeError guard for Codex null-output streams in |
Summary
Fixes a runtime crash where the openai-codex provider raises
TypeError: 'NoneType' object is not iterablemid-stream when the ChatGPT Codex backend emits aresponse.completedevent withoutput=None(not[]).Refs #5678.
Root cause
OpenAI SDK's
parse_response(openai/lib/_parsing/_responses.py:61) iteratesresponse.outputwithout a None guard:When the backend at
chatgpt.com/backend-api/codexemitsresponse.completedwithoutput=None, this crashes insideaccumulate_eventbefore Hermes' existing empty-output workarounds (run_codex_streamlines 248-266) can fire — those only handleoutput == []afterget_final_response(), which never returns because the SDK raises first.Full traceback (captured from
gpt-5.5Discord conversation):Fix
Catch
TypeErrorin the stream loop. If we have accumulated text deltas and aren't in a tool-call stream, synthesize aSimpleNamespaceresponse 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_fallbackso the outer retry loop has something useful to work with.Test plan
gpt-5.5via Discord gateway withopenai-codexprovider — confirmedNoneTypetraceback before patch.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.gpt-5.3-codex,gpt-5.4-mini— same provider, expected same fix coverage.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
title_generatoraux 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.🤖 Generated with Claude Code