fix: handle null response.output in parse_response#3441
Conversation
When a backend sends 'response.output: null' in a response.completed event (e.g., chatgpt.com Codex backend), parse_response would crash with TypeError: 'NoneType' object is not iterable. This one-line fix coerces None to an empty list, allowing the stream to complete gracefully. Consumers that track output_item.done events can still backfill from their collected items. Fixes openai#3325
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d91d0d75c2
ℹ️ 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".
| output_list: List[ParsedResponseOutputItem[TextFormatT]] = [] | ||
|
|
||
| for output in response.output: | ||
| for output in (response.output or []): |
There was a problem hiding this comment.
Preserve streamed output for null completed responses
When this runs from ResponseStreamState.accumulate_event() for a response.completed event, event.response.output == None is now converted to an empty final ParsedResponse.output even though the stream state may already contain items accumulated from prior response.output_item.added / text delta events. In that null-output backend scenario, consumers of response.completed.response, get_final_response().output, or get_final_response().output_text silently get an empty response instead of the text/tool calls they just streamed, so the stream no longer crashes but still drops the completed result.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Thanks for raising this! You're correct that a null in was overwriting the accumulated stream content, resulting in empty final responses.
The fix preserves accumulated items from prior / text delta events when is null, so consumers get the complete streamed result rather than an empty response. The null output case now only applies when there's genuinely nothing streamed.
Happy to adjust the approach if you prefer a different strategy for handling this edge case!
…ull output When response.completed arrives with null output, items accumulated from prior streaming events are now preserved by reconstructing the response with the snapshot output.
Summary
parse_response()insrc/openai/lib/_parsing/_responses.pyiteratesresponse.outputwithout checking forNone. The chatgpt.com Codex backend (https://chatgpt.com/backend-api/codex) sometimes sendsresponse.output: nullin the consolidatedresponse.completedevent — even when validresponse.output_item.doneevents were streamed earlier in the same response. The SDK then raisesTypeError: 'NoneType' object is not iterableinside the stream accumulator, killing the entire stream before the consumer can read the deltas.Fix
One-line defensive iteration in
parse_response:With this patch the stream completes,
get_final_response()returns aResponsewithoutput=[], and consumers that already trackoutput_item.doneevents can backfill the response from their own collected items. No regression on responses that legitimately haveoutput: list.Tests
Added
tests/lib/test_parsing_responses.pywith three tests:test_parse_response_with_none_output— verifies null output doesn't crash and returns empty output listtest_parse_response_with_empty_list_output— verifies empty list still workstest_parse_response_with_message_output— verifies normal output items still parse correctlyFixes #3325