Skip to content

fix(parsing): handle null response.output in parse_response#3322

Open
SteveYuOWO wants to merge 1 commit into
openai:mainfrom
SteveYuOWO:fix/parse-response-null-output
Open

fix(parsing): handle null response.output in parse_response#3322
SteveYuOWO wants to merge 1 commit into
openai:mainfrom
SteveYuOWO:fix/parse-response-null-output

Conversation

@SteveYuOWO
Copy link
Copy Markdown

This pull request makes parse_response in src/openai/lib/_parsing/_responses.py robust to terminal response.completed events whose response.output is null instead of [].

Without this change, the SDK raises TypeError: 'NoneType' object is not iterable during stream accumulation (accumulate_eventparse_response), so the streamed deltas the client already received via response.output_text.delta / response.output_item.done are lost and the caller's for event in stream: loop dies before get_final_response() is ever reached.

This has been observed on the ChatGPT codex backend (chatgpt.com/backend-api/codex) for newer codex models, where it breaks every hermes-agent conversation turn against that backend with error_type=TypeError ... summary='NoneType' object is not iterable. Tracking issues in the downstream consumer: NousResearch/hermes-agent#33059 (which explicitly identifies this SDK function as the broken layer) and #32991. Client-side workarounds in hermes around get_final_response() are unreachable because the crash happens earlier, inside the stream iterator.

Iterating over (response.output or []) defensively treats None as empty, matching the existing empty-list path. Added a regression test in tests/lib/responses/test_responses.py.

The Response API contract types ``output`` as ``List[ResponseOutputItem]``,
but some backends (observed on the ChatGPT codex backend for newer models)
emit a terminal payload with ``output: null``. Pydantic does not coerce
None to ``[]`` because the field is annotated as a list rather than
Optional, so ``parse_response()`` raises ``TypeError: 'NoneType' object
is not iterable`` during stream accumulation. Treat None defensively as
an empty list so streaming clients can recover from the deltas they
already received via ``response.output_text.delta``
and ``response.output_item.done`` events.
@SteveYuOWO SteveYuOWO requested a review from a team as a code owner May 27, 2026 08:23
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cef5e9ecd7

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

# which would raise ``TypeError: 'NoneType' object is not iterable``
# here. Treat None defensively as an empty list so streaming clients
# can recover from the deltas they already received.
for output in (response.output or []):
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve streamed output when the terminal payload is null

When this path is reached from streaming, ResponseStreamState.accumulate_event() calls parse_response(..., response=event.response) for the response.completed event, and get_final_response() later returns that _completed_response. For the backend case described in this change, the terminal event.response.output is None even though earlier response.output_item.added / delta events populated the stream snapshot, so treating it as [] makes the final ResponseCompletedEvent.response and stream.get_final_response() lose all accumulated message/tool output. This avoids the crash but still breaks callers that rely on the final parsed response for exactly the null-terminal-output stream being fixed.

Useful? React with 👍 / 👎.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant