Skip to content

fix(parsing): guard against None response.output in parse_response and streaming accumulator - #3420

Closed
Vitaliy-Pikalo wants to merge 2 commits into
openai:mainfrom
Vitaliy-Pikalo:fix/parse-response-null-output
Closed

fix(parsing): guard against None response.output in parse_response and streaming accumulator#3420
Vitaliy-Pikalo wants to merge 2 commits into
openai:mainfrom
Vitaliy-Pikalo:fix/parse-response-null-output

Conversation

@Vitaliy-Pikalo

Copy link
Copy Markdown

Summary

When a backend (e.g. the Codex endpoint) sends response.output: null in the terminal response.completed SSE event, the SDK crashes with TypeError: 'NoneType' object is not iterable because parse_response() iterates response.output unconditionally.

This PR applies two complementary fixes:

1. Null guard in parse_response() (src/openai/lib/_parsing/_responses.py)

-    for output in response.output:
+    for output in (response.output or []):

Prevents the TypeError for any caller — streaming or non-streaming — when response.output is None.

2. Snapshot fallback in the streaming accumulator (src/openai/lib/streaming/responses/_responses.py)

When the response.completed payload has output=null, but the accumulator's snapshot already has the reconstructed output items from the preceding response.output_item.added/done events, use those items instead of losing the data:

         elif event.type == "response.completed":
-            self._completed_response = parse_response(
-                text_format=self._text_format,
-                response=event.response,
-                input_tools=self._input_tools,
-            )
+            completed_response = event.response
+            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]},
+                )
+            self._completed_response = parse_response(
+                text_format=self._text_format,
+                response=completed_response,
+                input_tools=self._input_tools,
+            )

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

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
@Vitaliy-Pikalo
Vitaliy-Pikalo requested a review from a team as a code owner June 22, 2026 14:36

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

Copy link
Copy Markdown

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: 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]},

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 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 👍 / 👎.

@marcuswood-oai

Copy link
Copy Markdown
Contributor

Thank you for the contribution! The null-output parsing and stream recovery fix has landed in #3345, so I am closing this PR as superseded. We appreciate your help!

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

Labels

None yet

Projects

None yet

2 participants