fix(parsing): guard against None response.output in parse_response and streaming accumulator#3420
Conversation
When the API sends `response.output: null` in a response.completed event, parse_response() would raise TypeError: 'NoneType' object is not iterable. Fixes openai#3314, openai#3321, openai#3325
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f633aad112
ℹ️ About Codex in GitHub
Your team has set up Codex to 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 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if completed_response.output is None and snapshot.output: | ||
| completed_response = construct_type_unchecked( | ||
| type_=type(event.response), | ||
| value={**event.response.to_dict(), "output": [item.to_dict() for item in snapshot.output]}, |
There was a problem hiding this comment.
Avoid finalizing from partial snapshots
When response.completed has output: null, this serializes snapshot.output as if it were the final output, but the snapshot is only partially reconstructed: accumulate_event() folds in added items, content parts, text deltas, and function-call argument deltas, while other finalizing updates such as response.output_item.done (which carries the completed item/status) and custom-tool/code-interpreter/reasoning/annotation done or delta events are just passed through. In those streams, get_final_response() can now return stale or incomplete items, such as messages still marked in_progress or tool-call fields missing their final values, instead of the completed response.
Useful? React with 👍 / 👎.
Summary
When a backend (e.g. the Codex endpoint) sends
response.output: nullin the terminalresponse.completedSSE event, the SDK crashes withTypeError: 'NoneType' object is not iterablebecauseparse_response()iteratesresponse.outputunconditionally.This PR applies two complementary fixes:
1. Null guard in
parse_response()(src/openai/lib/_parsing/_responses.py)Prevents the
TypeErrorfor any caller — streaming or non-streaming — whenresponse.outputisNone.2. Snapshot fallback in the streaming accumulator (
src/openai/lib/streaming/responses/_responses.py)When the
response.completedpayload hasoutput=null, but the accumulator's snapshot already has the reconstructed output items from the precedingresponse.output_item.added/doneevents, use those items instead of losing the data:Fix 1 alone prevents the crash but returns
output=[], discarding all streamed items. Fix 2 preserves the actual content the user already received over the wire.Fixes
Closes #3314
Closes #3321
Closes #3325