Skip to content

fix: raise APIError for top-level Responses API error events during streaming - #3587

Open
mayuriphad wants to merge 1 commit into
openai:mainfrom
mayuriphad:fix/streaming-response-error-event
Open

fix: raise APIError for top-level Responses API error events during streaming#3587
mayuriphad wants to merge 1 commit into
openai:mainfrom
mayuriphad:fix/streaming-response-error-event

Conversation

@mayuriphad

Copy link
Copy Markdown

Summary

Fixes #2487.

Stream.__stream__ / AsyncStream.__stream__ in src/openai/_streaming.py only raise an APIError when the SSE event body has a nested "error" key:

if is_mapping(data) and data.get("error"):
    ...
    raise APIError(..., body=data["error"])

But per the Responses API streaming error spec, the error event carries its fields (type, code, message, param, sequence_number) directly on the event body -- there is no nested "error" key:

{"type": "error", "code": "...", "message": "Something went wrong", "param": null, "sequence_number": 1}

Because data.get("error") is None for these events, the condition never matches, so the event is silently passed to process_data(...) as if it were ordinary stream data instead of raising a clear APIError. Depending on the expected type being cast to, this can surface as a confusing Pydantic validation error deep in the stack instead of the server's actual error message.

Fix

In both the sync Stream and AsyncStream __stream__ methods, also treat sse.event == "error" as an error condition, falling back to the top-level event body for the message/body when there's no nested "error" key (kept for backwards compatibility with any caller still relying on that shape):

if is_mapping(data) and (data.get("error") or sse.event == "error"):
    ...
    error = data.get("error") if is_mapping(data.get("error")) else data
    message = error.get("message") if is_mapping(error) else None
    ...
    raise APIError(
        message=message,
        request=self.response.request,
        body=data.get("error") if data.get("error") is not None else data,
    )

This is a minimal, targeted change -- no unrelated refactors.

Tests

Added regression tests in tests/test_streaming.py (for both Stream and AsyncStream, via the existing sync parametrization):

  • test_response_error_event_raises_api_error -- a top-level response.error-shaped SSE error event raises APIError with the server's message.
  • test_response_error_event_without_message_uses_default -- the same event without a message field raises APIError with the existing default message.

Both tests fail on main (DID NOT RAISE APIError) and pass with this fix.

$ pytest tests/test_streaming.py -q
24 passed

ruff check and ruff format --check pass on both changed files.

…treaming

Stream.__stream__ / AsyncStream.__stream__ only raised an APIError when the
SSE event body had a nested "error" key. Per the Responses API streaming
spec, the "error" event carries its fields (type, code, message, param,
sequence_number) directly on the event body with no nested "error" key, so
these events were silently yielded as regular data instead of raising,
leading to confusing downstream validation errors instead of a clear
APIError with the server's message.

Fixes openai#2487
Copilot AI lite review requested due to automatic review settings August 9, 2026 17:44
@mayuriphad
mayuriphad requested a review from a team as a code owner August 9, 2026 17:44

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@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: 698bbc5c9b

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

Comment thread src/openai/_streaming.py
else:
data = sse.json()
if is_mapping(data) and data.get("error"):
if is_mapping(data) and (data.get("error") or sse.event == "error"):

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 Preserve Assistant error events

When this shared stream path is used by the Assistants endpoints, synthesize_event_and_data=True causes a raw SSE event: error body to be wrapped into the documented AssistantStreamEvent shape (event == "error", data: ErrorObject). This new condition raises before that wrapping, so an Assistants stream error that consumers previously could handle via iteration or on_event now aborts as APIError; please scope the new top-level handling to Responses streams or skip it when synthesizing event/data. This is an unrelated exported-stream behavior change from a handwritten core helper.

Useful? React with 👍 / 👎.

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.

Responses API error handling reads error.message, but spec says message is top-level

2 participants