Skip to content

Improve streaming error handling and robustness#2960

Open
edenfunf wants to merge 2 commits intoopenai:mainfrom
edenfunf:improve-streaming-error-handling
Open

Improve streaming error handling and robustness#2960
edenfunf wants to merge 2 commits intoopenai:mainfrom
edenfunf:improve-streaming-error-handling

Conversation

@edenfunf
Copy link

Summary

This PR improves error handling in the streaming implementation, fixing a few bugs and improving robustness:

Test plan

  • Added TestServerSentEventJson - tests for .json() error handling (valid JSON, empty data, malformed JSON, truncated JSON)
  • Added TestCheckStreamError - tests for the error detection helper (no error, error event, error in data field, top-level message fallback, string error field, missing message)
  • Added TestParseSSEData - tests for the JSON parsing helper (valid data, empty data, None data, malformed data)
  • Added test_invalid_utf8_skipped - verifies invalid UTF-8 bytes are skipped and subsequent events still parse
  • Added test_error_event_handling - verifies explicit error events are detected (previously unreachable)
  • All 38 streaming tests pass, all 170 related tests pass

- Fix unreachable error check in stream iteration: the `sse.event == "error"`
  condition was nested inside the `sse.event.startswith("thread.")` branch,
  making it impossible to match (relates to openai#2796)

- Extract duplicated stream error handling into `_check_stream_error()` helper,
  reducing 4 copies of the same error extraction logic to a single function

- Add `_parse_sse_data()` helper with proper JSON parse error handling, so
  malformed SSE data produces clear error messages instead of raw JSONDecodeError
  (relates to openai#2722)

- Improve `ServerSentEvent.json()` to include event type and data preview in
  parse error messages for easier debugging

- Add UTF-8 decode error handling in SSE decoder to gracefully skip invalid
  bytes instead of crashing the entire stream

- Narrow `except Exception` to `except httpx.HTTPError` in the retry loop
  so non-network exceptions (e.g. KeyboardInterrupt, CancelledError,
  Celery SoftTimeLimitExceeded) are no longer caught and retried (relates to openai#2737)

- Also check top-level `message` field in stream error responses, per API spec
  (relates to openai#2487)

- Add comprehensive tests for all new behavior
@edenfunf edenfunf requested a review from a team as a code owner March 11, 2026 05:49
Copy link

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

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: 3a4fc95af9

ℹ️ 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 on lines +128 to +129
if data is None:
continue

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Surface empty error events instead of skipping them

In Stream.__stream__, events with empty data are unconditionally skipped before _check_stream_error runs, so an SSE frame like event: error with no payload gets silently ignored rather than terminating the stream with an error. This means backend/proxy failures that emit empty error events can be treated as successful stream completion, which is a regression from the previous fail-fast behavior; the same control flow exists in AsyncStream.__stream__ as well.

Useful? React with 👍 / 👎.

Move _check_stream_error() before the `if data is None: continue` guard
in both Stream.__stream__ and AsyncStream.__stream__, so that an explicit
`event: error` with no data payload still raises APIError rather than
being silently skipped. Also handle None data in _check_stream_error
itself for robustness.

Added tests that verify bare error events (no payload) raise at both
the SSE decoder level and the full Stream iteration level.
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.

1 participant