Skip to content

fix(files): stream content in 1 MB chunks to prevent ConnectionResetError on large Batch API files#2985

Open
NIK-TIGER-BILL wants to merge 1 commit intoopenai:mainfrom
NIK-TIGER-BILL:fix/files-chunked-streaming-download
Open

fix(files): stream content in 1 MB chunks to prevent ConnectionResetError on large Batch API files#2985
NIK-TIGER-BILL wants to merge 1 commit intoopenai:mainfrom
NIK-TIGER-BILL:fix/files-chunked-streaming-download

Conversation

@NIK-TIGER-BILL
Copy link

Summary

Fixes #2959

HttpxBinaryResponseContent.content read the entire HTTP response body at once via response.content. For large Batch API result files (>~200 MB), this triggered a ConnectionResetError because long-lived server connections were reset before the single large read completed.

Changes

Replaced return self.response.content with a chunked loop over response.iter_bytes(chunk_size=1 MB). The return type and behaviour are unchanged — the property still returns bytes — but data is now read incrementally, keeping the connection alive.

Before

@property
def content(self) -> bytes:
    return self.response.content  # reads entire body at once → ConnectionResetError on >200 MB

After

@property
def content(self) -> bytes:
    _CHUNK_SIZE = 1024 * 1024  # 1 MB
    buf = bytearray()
    for chunk in self.response.iter_bytes(chunk_size=_CHUNK_SIZE):
        buf.extend(chunk)
    return bytes(buf)

Testing

Users downloading Batch API result files with client.files.content(file_id).content should no longer see ConnectionResetError for large files.

…rror on large files

Fixes openai#2959

Batch API result files larger than ~200 MB caused ConnectionResetError
because HttpxBinaryResponseContent.content read the entire HTTP body
at once via response.content. Long-lived connections get reset by the
server before a single large read completes.

Fix: replace response.content with a loop over response.iter_bytes()
using 1 MB chunks. The return type and semantics are unchanged — the
property still returns bytes — but the data is read incrementally,
keeping the connection alive throughout the download.
@NIK-TIGER-BILL NIK-TIGER-BILL requested a review from a team as a code owner March 18, 2026 07:22
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: 09befca722

ℹ️ 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 +403 to +406
_CHUNK_SIZE = 1024 * 1024 # 1 MB
buf = bytearray()
for chunk in self.response.iter_bytes(chunk_size=_CHUNK_SIZE):
buf.extend(chunk)

Choose a reason for hiding this comment

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

P1 Badge Chunk the body before APIResponse.parse() reads it

For the normal client.files.content(...) flow, HttpxBinaryResponseContent is only constructed after APIResponse.parse() has already called self.read() on the httpx.Response (src/openai/_response.py:323-339). Any large-file download failure therefore happens before this property is ever reached, so moving the loop here does not change the code path that actually reads from the socket and will not fix the reported ConnectionResetError for the non-streaming binary-download API.

Useful? React with 👍 / 👎.

Comment on lines +404 to +407
buf = bytearray()
for chunk in self.response.iter_bytes(chunk_size=_CHUNK_SIZE):
buf.extend(chunk)
return bytes(buf)

Choose a reason for hiding this comment

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

P2 Badge Avoid copying the full binary payload a second time

In the same non-streaming path, the response body has already been buffered by the time this wrapper is created (src/openai/_response.py:323-339), so iter_bytes() here just replays response._content. Rebuilding that into a bytearray and then bytes makes an extra full-size copy of every file; for the 200MB+ downloads mentioned in the commit message, .content now adds hundreds of MB of peak memory and extra CPU where return self.response.content previously reused the existing buffer.

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.

bug: Batch API result file download fails for large outputs (>200MB) with ConnectionResetError

1 participant