Skip to content

fix: drain remaining stream bytes after [DONE] event to prevent connection force-close#3510

Open
zerafachris wants to merge 3 commits into
openai:mainfrom
zerafachris:main
Open

fix: drain remaining stream bytes after [DONE] event to prevent connection force-close#3510
zerafachris wants to merge 3 commits into
openai:mainfrom
zerafachris:main

Conversation

@zerafachris

Copy link
Copy Markdown

Problem

When streaming SSE responses, after receiving data: [DONE], the Stream.__stream__ method immediately calls response.close() without draining remaining bytes from the iterator. This means the HTTP/1.1 chunked terminator (0\r\n\r\n) may still be buffered, causing httpcore to destroy the connection instead of gracefully returning it to the pool.

This is a regression from commit 6132922c which removed the drain logic. The finally block calls response.close() without first consuming remaining bytes.

Fix

In the finally block of both Stream.__stream__ and AsyncStream.__stream__, before calling response.close()/await response.aclose(), drain any remaining bytes from the iterator to ensure the chunked transfer completes cleanly.

Changes

  • src/openai/_streaming.py: Drain iterator in finally block for sync Stream.__stream__ (add for _ in iterator: pass)
  • src/openai/_streaming.py: Drain iterator in finally block for async AsyncStream.__stream__ (add async for _ in iterator: pass)

@zerafachris
zerafachris requested a review from a team as a code owner July 16, 2026 08:49

@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: 5dc9a3fc10

ℹ️ 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 Outdated
The drain added in the previous commit ran unconditionally in the
`finally` block, so it also executed when the caller stopped before the
`[DONE]` event -- e.g. breaking out of iteration and letting the stream
be collected (GeneratorExit), an APIError, or async cancellation. In
those cases it consumed the rest of the network stream instead of
closing it, which for a long-running or stalled completion blocks until
the server finishes and defeats the close-on-incomplete-consumption
behaviour.

Track whether `[DONE]` was actually observed and only drain on that
path; otherwise close the response as before. Applied to both Stream and
AsyncStream.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@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: c789ceb105

ℹ️ 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
Comment on lines +118 to 121
if terminated:
for _ in iterator:
pass
response.close()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve successful completion when trailing drain fails

If the server has already sent a valid [DONE] event but closes/reset the connection before its final transport bytes arrive, advancing iterator here can raise an httpx transport/protocol error. That turns a semantically completed stream into a failure during list(stream)/normal iteration, and because the exception exits this finally, response.close() is skipped as well. Drain failures after [DONE] should still close the response and not replace the successful completion (the async drain has the same issue).

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.

1 participant