Skip to content

fix(responses): guard parser/output_text when response.output is null - #3323

Open
jackpalm88 wants to merge 1 commit into
openai:mainfrom
jackpalm88:fix/response-output-none-guard
Open

jackpalm88 wants to merge 1 commit into
openai:mainfrom
jackpalm88:fix/response-output-none-guard

Conversation

@jackpalm88

@jackpalm88 jackpalm88 commented May 27, 2026 •

Copy link
Copy Markdown

Fix parse_response crash when response.output is None

Summary

This patch hardens the OpenAI Python SDK Responses parsing path against output=None payloads.

Observed failure (openai==2.33.0):

  • openai/lib/_parsing/_responses.py iterates response.output unconditionally.
  • If a Responses backend returns output: null, parser raises:
    • TypeError: 'NoneType' object is not iterable

Patch behavior:

  • Treat response.output is None as an empty list in parser loop.
  • Apply same guard in Response.output_text property for consistency.

Why

In rare edge cases, a Responses payload may contain output: null; the SDK should handle this defensively.

Changes

  1. src/openai/lib/_parsing/_responses.py
- for output in response.output:
+ for output in (response.output or []):
  1. src/openai/types/responses/response.py
- for output in self.output:
+ for output in (self.output or []):

Expected result

  • parse_response(...) no longer crashes when response.output is None.
  • Parsed response behavior is equivalent to an empty output list.
  • Response.output_text returns "" instead of crashing when self.output is None.

Suggested tests

  • Add regression test where Response.model_construct(..., output=None) is passed to parse_response.
  • Assert parsed response returns output=[].
  • Assert Response(..., output=None).output_text == "".

@jackpalm88
jackpalm88 requested a review from a team as a code owner May 27, 2026 16:58

@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: 946947ce15

ℹ️ 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".

"""
texts: List[str] = []
for output in self.output:
for output in (self.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 Make output nullable for strict validation

When the API returns output: null and the client is configured with _strict_response_validation=True, this guard is never reached: _process_response_data calls validate_type() before constructing the Response, and Response.output is still declared as List[ResponseOutputItem], so Pydantic rejects None up front. This means strict-validation users still get an APIResponseValidationError for the null-output response this change is trying to tolerate; the field type needs to allow None as well as the runtime guard.

Useful? React with 👍 / 👎.

@jackpalm88

Copy link
Copy Markdown
Author

Happy to adjust the regression tests if maintainers prefer a different test location or setup.

@jnohclee-rgb

Copy link
Copy Markdown

AI-assisted independent offline check at 946947ce15c6a4740626e2166b3fc57ef926720f against parent 09ece2ee0aa73077d04ac3669d101120076b52fb. Six constructed Response shapes exercise actual output_text and parse_response:

  • output=None: both helpers raise TypeError before; this head returns empty text / empty parsed output.
  • Empty list and one valid text message: unchanged results; status='incomplete' and fictional metadata remain preserved.
  • output=False or 0: previously TypeError, now silently treated as empty, because or [] handles all falsy values rather than only None.
  • Message with content=None: both helpers still raise TypeError on both sources; this PR is a top-level output guard, not a general null-tolerant parser.

If only JSON null is intended to be recoverable, [] if response.output is None else response.output (and the equivalent property check) narrows the behavior and leaves malformed falsy values distinguishable. Suggested controls: null, valid empty/list output, metadata/status retention, and an invalid falsy value. The invalid shapes below are deliberately constructed with SDK construct(), bypassing validation; they demonstrate helper behavior, not documented valid API responses.

Scope: six fictional constructed cases, actual helpers, Python3.14/Pydantic2.13.5 and shared local dependencies with archived sources. No provider responses, streaming or full SDK suite; network denied. This is not evidence that the provider emits False,0 or null nested content.

Standalone reproducer
import json
from openai.types.responses import Response,ResponseOutputMessage,ResponseOutputText
from openai.lib._parsing._responses import parse_response
from openai import omit
rows=[]
message=ResponseOutputMessage.construct(type='message',id='fictional_msg',role='assistant',status='completed',content=[ResponseOutputText.construct(type='output_text',text='hello',annotations=[])])
for label,out in [('null',None),('empty',[]),('message',[message]),('false',False),('zero',0),('missing_content',[ResponseOutputMessage.construct(type='message',id='fictional',role='assistant',status='completed',content=None)])]:
 response=Response.construct(id='fictional_response',object='response',created_at=0,model='fictional_model',output=out,status='incomplete',metadata={'fictional':'retained'},incomplete_details={'reason':'max_output_tokens'})
 row={'case':label}
 try:row['output_text']=response.output_text
 except Exception as e:row['text_error']=type(e).__name__
 try:
  parsed=parse_response(response=response,text_format=omit,input_tools=[])
  row.update(parsed_count=len(parsed.output),status=parsed.status,metadata=parsed.metadata)
 except Exception as e:row['parse_error']=type(e).__name__
 rows.append(row)
print(json.dumps(rows))

This branch has not been deployed

No deployments
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.

2 participants