From 2b34be89cf0492a0fb86a0d6840e64aa4ec78cc3 Mon Sep 17 00:00:00 2001 From: Mayuri Date: Sun, 9 Aug 2026 23:08:41 +0530 Subject: [PATCH] fix(responses): handle null output in parse_response Some backends (e.g. the chatgpt.com Codex backend used by the Codex CLI) can send `output: null` on the `response.completed` event even though the schema declares `output` as a non-nullable list. This caused parse_response() to raise `TypeError: 'NoneType' object is not iterable`, killing the entire stream before consumers could read already-accumulated deltas. Coerce a null output to an empty list before iterating, matching the behavior implied by get_final_response() returning an otherwise valid Response/ParsedResponse. Fixes #3325 --- src/openai/lib/_parsing/_responses.py | 2 +- tests/lib/responses/test_responses.py | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/openai/lib/_parsing/_responses.py b/src/openai/lib/_parsing/_responses.py index c607587ec1..81e6b2b983 100644 --- a/src/openai/lib/_parsing/_responses.py +++ b/src/openai/lib/_parsing/_responses.py @@ -58,7 +58,7 @@ def parse_response( ) -> ParsedResponse[TextFormatT]: output_list: List[ParsedResponseOutputItem[TextFormatT]] = [] - for output in response.output: + for output in response.output or []: if output.type == "message": content_list: List[ParsedContent[TextFormatT]] = [] for item in output.content: diff --git a/tests/lib/responses/test_responses.py b/tests/lib/responses/test_responses.py index 4ed6dff47d..d371900d8b 100644 --- a/tests/lib/responses/test_responses.py +++ b/tests/lib/responses/test_responses.py @@ -72,6 +72,18 @@ def test_parse_response_preserves_program_items(item: dict[str, object]) -> None assert parsed.output[0].to_dict() == item +def test_parse_response_with_null_output() -> None: + # Regression test for https://github.com/openai/openai-python/issues/3325 + # Some backends (e.g. the chatgpt.com Codex backend) can send `output: null` + # in the `response.completed` event, even though the schema declares `output` + # as a non-nullable list. `parse_response` should not crash in this case. + response = construct_type_unchecked(type_=Response, value={"output": None}) + + parsed = parse_response(text_format=omit, input_tools=omit, response=response) + + assert parsed.output == [] + + @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) def test_stream_method_definition_in_sync(sync: bool, client: OpenAI, async_client: AsyncOpenAI) -> None: checking_client: OpenAI | AsyncOpenAI = client if sync else async_client