Skip to content

fix(codex): translate SDK TypeError on output=None to classified error (#33976) - #34265

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

fix(codex): translate SDK TypeError on output=None to classified error (#33976)#34265
Bartok9 wants to merge 1 commit into
NousResearch:mainfrom
Bartok9:fix/codex-output-none-typeerror-33976

Conversation

@Bartok9

@Bartok9 Bartok9 commented May 29, 2026

Copy link
Copy Markdown
Contributor

Fixes #33976.

Problem

The ChatGPT Pro Codex OAuth subscription path (chatgpt.com/backend-api/codex/responses) has been observed intermittently returning HTTP 200 with response.output = None on the terminal response.completed event. The OpenAI SDK's internal parse_response then raises:

TypeError: 'NoneType' object is not iterable
  at _parsing/_responses.py:61 → for output in response.output:

This propagates as a raw stack trace through Hermes to the user-facing layer:

  • Slack Gateway fails on any Codex request
  • Cron jobs using Codex fail with cascading TypeError messages
  • Users see SDK-internal stack traces instead of a classified provider error
  • Recovery requires manually switching Hermes to a non-Codex provider

Why Hermes' existing raw-event consumer doesn't catch it

Hermes already uses responses.create(stream=True) and consumes raw events directly to avoid relying on the SDK's typed-response reconstruction. However, the SDK still does its own internal parse_response on stream close — so the TypeError raised from for output in response.output: still bubbles up to our _consume_codex_event_stream caller.

Fix

Defensive TypeError catch around the stream consumer at both call sites:

  • agent/auxiliary_client.py (Slack / cron gateway path)
  • agent/codex_runtime.py (main agent loop path)

When the exception matches the specific 'NoneType' object is not iterable signature, translate to a classified RuntimeError with an actionable message:

except TypeError as exc:
    if "NoneType" in str(exc) and "iterable" in str(exc):
        raise RuntimeError(
            "Codex backend returned a malformed response (output=None on HTTP 200). "
            "Known intermittent issue with the chatgpt.com/backend-api/codex endpoint "
            "— retry or fall back to a non-Codex provider."
        ) from exc
    raise   # any other TypeError indicates a real bug — propagate uncaught

The catch is intentionally narrow — only the exact failure-mode message gets translated. Any other TypeError propagates because it indicates a real bug, not a backend regression. The original cause is preserved via raise ... from exc so debug logging can still see the SDK trace.

Tests

4 new tests in tests/agent/test_codex_malformed_response_33976.py:

Test Verifies
test_typeerror_with_none_iterable_is_translated (aux) Slack/cron path translates correctly
test_other_typeerror_messages_still_propagate (aux) Narrow catch — other TypeErrors propagate
test_runtime_translates_none_iterable_typeerror (runtime) Main-loop path translates correctly
test_unrelated_typeerror_still_propagates_in_runtime Narrow catch (runtime side)
$ pytest tests/agent/test_codex_malformed_response_33976.py
4 passed in 0.09s

Credit to @szuhodov for the precise root-cause analysis including the exact SDK file/line and the workaround config.

Co-authored-by: Cursor cursoragent@cursor.com

NousResearch#33976)

Fixes NousResearch#33976. The ChatGPT Pro Codex OAuth subscription path
(chatgpt.com/backend-api/codex/responses) has been observed
intermittently returning HTTP 200 with response.output=None on the
terminal response.completed event. The OpenAI SDK's internal
parse_response then raises:

  TypeError: 'NoneType' object is not iterable

…from 'for output in response.output:' in
_parsing/_responses.py:61, BEFORE Hermes's adapter can intervene.
This propagates up as a raw stack trace to the user-facing layer,
breaking Slack gateway requests and any cron job using the Codex
provider until the user manually switches to OpenRouter/Claude/etc.

Hermes already consumes raw events directly to avoid relying on the
SDK's typed-response reconstruction (the streaming kwargs return an
event iterator), but the SDK still does its own internal parse on
stream close — so the TypeError still surfaces.

Fix: defensive TypeError catch around the stream consumer at both
call sites (auxiliary_client for Slack/cron, codex_runtime for
interactive). When the exception matches the specific 'NoneType is
not iterable' signature, translate to a classified RuntimeError with
an actionable message naming the endpoint and the workaround.

The catch is intentionally narrow — only the exact failure-mode
message gets translated. Any other TypeError propagates uncaught
because it indicates a real bug, not a backend regression.

Adds 4 regression tests in tests/agent/test_codex_malformed_response_33976.py
covering both call sites and the narrow-catch invariant.

Co-authored-by: Cursor <cursoragent@cursor.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 provider/openai OpenAI / Codex Responses API labels May 29, 2026
@teknium1

Copy link
Copy Markdown
Contributor

Thanks for this — closing as already fixed on main.

The Codex null-output crash (response.completed with output: null from the chatgpt.com backend → TypeError: 'NoneType' object is not iterable → surfaced as "non-retryable HTTP None") is resolved by commits already on main:

  • cb38ce28c — drop the SDK responses.stream() helper and consume events directly, rebuilding output from response.output_item.done events so the null terminal-event output is never iterated (refactor(codex): drop SDK responses.stream() helper; consume events directly #33042).
  • 43a3f119f — recover Codex streams with null output.
  • dc9d677d5 — classify TypeError('NoneType … not iterable') as a retryable provider-shape error so retry/fallback runs instead of killing the turn.

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.

@teknium1 teknium1 closed this Jun 30, 2026
@Bartok9

Bartok9 commented Jun 30, 2026

Copy link
Copy Markdown
Contributor Author

Makes sense — thanks for the detailed pointer to cb38ce2 / 43a3f11 / dc9d677. The direct-event consumption + retryable-shape classification on main is the cleaner fix; glad the path is hardened. Appreciate the credit in #33932, and no objection to closing here. 🙏

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 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.

openai-codex subscription backend returns HTTP 200 with response.output=None, causing Slack/cron failures

3 participants