Skip to content

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

Open

Description

@xwang049

Confirm this is an issue with the Python library and not an underlying OpenAI API

  • This is an issue with the Python library

Describe the bug

Describe the bug
When using the Batch API to download large result files (specifically when the output .jsonl exceeds ~200-300MB, e.g., 50k embedding rows), the download connection is prematurely closed by the peer.

The error typically manifests as:
httpcore.RemoteProtocolError: peer closed connection without sending complete message body (received X bytes, expected Y bytes)

To Reproduce
Create a Batch job with 50,000 embedding requests (text-embedding-3-small).

Wait for the batch to complete (status: completed).

Attempt to download the result file using the SDK:

Python

Standard SDK approach that fails

content = client.batches.retrieve_content(batch.output_file_id)
content.with_streaming_response.content()
Or using the requests library directly with the file URL.

The download will consistently fail after receiving a few hundred megabytes.

Expected behavior
The SDK should handle large file streaming robustly, or provide a built-in chunked download/retry mechanism for massive Batch outputs (1GB+).

Environment
OS: macOS 15.x

Python Version: 3.12.x

Additional context
Workaround: I've confirmed that splitting the 50k requests into smaller batches of 10k (resulting in ~200MB files) allows for stable downloads. This suggests a potential timeout or buffer limitation on the server-side proxy or within the SDK's streaming implementation for payloads exceeding a certain threshold.

I am currently implementing a manual chunked-download helper with exponential backoff to bypass this. but i dont think it;s optimal

To Reproduce

To Reproduce:

  1. Prepare a batch with ~50,000 rows (near the OpenAI limit)
  2. Submit to Batch API with endpoint /v1/embeddings
  3. Wait for status = completed
  4. Attempt to download result file via Files API Code Snippets:

sample code:

from openai import OpenAI
client = OpenAI()

Result file is ~1GB when batch has 50,000 embeddings

result_content = client.files.content(result_file_id)
result_text = result_content.text # fails silently or raises

#failed on streaming :
with client.files.with_streaming_response.content(result_file_id) as resp:
with open("result.jsonl", "wb") as f:
for chunk in resp.iter_bytes(chunk_size=1024 * 1024):
f.write(chunk)

raises: peer closed connection without sending complete message body

(received 940216419 bytes, expected 1056098116)

#same for requests:
import requests
response = requests.get(
f"https://api.openai.com/v1/files/{result_file_id}/content",
headers={"Authorization": f"Bearer {api_key}"},
stream=True,
timeout=(10, 300)
)

same error, connection drops around 500-1000MB

Code snippets

OS

macOS

Python version

Python Version: 3.12.x

Library version

2.24.0

Activity

  1. passionworkeer commented on Mar 11, 2026

    @passionworkeer

    This is a significant issue for production systems handling large outputs. Any ETA on a fix? In the meantime, could chunking the download help mitigate this?

  2. xwang049 commented on Mar 11, 2026

    @xwang049
    Author

    chunking to 200MB is working for sure, but this shouldn't be like this low threshold

  3. added a commit that references this issue on Mar 18, 2026
    09befca
  4. AhsanSheraz commented on Mar 24, 2026

    @AhsanSheraz

    Hey @xwang049 @passionworkeer ,

    You can also try pyresilience, to add retry/timeout around the download operation. This library have all the 7 resilience patterns, wit 1 decorator.
    Feel free to open PR, if you need any further assistance, do let me know.

    Pypi: https://pypi.org/project/pyresilience/
    Docs: https://pyresilience.readthedocs.io

  5. khalidsaidi commented on Mar 25, 2026

    @khalidsaidi

    A2ABench has an accepted answer for this imported thread.

    • Thread: https://a2abench-api.web.app/q/cmmpna6qs00hd1401p8gsondy
    • Accepted at: 2026-03-25T17:19:05.835Z
    • Accepted answer agent: ai village
    • Answer preview: "This does not look like a Python-SDK-specific bug. The strongest evidence is in the report itself: the same failure happens with raw requests.get(..., stream=True), not just with openai-python the error is a transport-level disconnect / incomplete body: httpcore.RemoteProtocolErr"
  6. CacinieP commented on Aug 31, 2026

    @CacinieP

    I'd like to take this one. Quick summary of the direction I plan to pursue, building on what's already established in this thread:

    • The failure is a mid-transfer cutoff on a long-lived single connection (it reproduces with plain requests, and the reporter's chunked Range workaround works). So restarting the download from byte 0 — which is what max_retries does today for client.files.content() — can never make progress when a transfer dies at a deterministic size/duration threshold: every retry attempt gets killed at roughly the same point.
    • fix(files): stream content in 1 MB chunks to prevent ConnectionResetError on large Batch API files #2985 changed LegacyAPIResponse.content to loop iter_bytes() in 1 MB chunks, but httpx already reads the body incrementally, so that doesn't change the network behavior (and as a side effect it makes .content fail on a second access).

    Plan: teach the retry loop in _base_client.py to resume an interrupted non-streamed GET download with Range: bytes=<received>- when the server advertised Accept-Ranges: bytes, instead of restarting from zero:

    • attempt 1 behaves exactly as today (full download);
    • if the body read dies mid-transfer with a retryable transport error, subsequent attempts send a Range header for the bytes already received; a 206 response appends to the partial body, a 200 response (server ignored the range) replaces it;
    • servers that don't advertise Accept-Ranges: bytes keep today's restart-from-zero behavior unchanged;
    • no public API changes; focused sync + async tests with a mock transport that drops the connection mid-body.

    This keeps the existing max_retries semantics — it just makes retries actually make forward progress for large file downloads.

  7. CacinieP commented on Sep 1, 2026

    @CacinieP

    @jbeckwith-oai friendly nudge on this issue — I've opened a candidate fix in #3764 and would appreciate a steer on whether the shape is acceptable before it goes further.

    Quick summary of what it does and doesn't do:

    Two things I'd be grateful for: the PR's workflows need an approval to run (first-time contributor here), and if you'd rather see this implemented at the template/generator level — or not in the SDK at all — that's a perfectly good answer and I'm happy to hand over the analysis and tests, or rework the PR in whatever direction you prefer.

  8. added a commit that references this issue on Sep 30, 2026
    735ff7a
  9. CacinieP commented on Oct 1, 2026

    @CacinieP

    PR #3764 proposes a fix for this with byte-range resume of interrupted downloads (works when the server advertises Accept-Ranges: bytes) — linking here for anyone tracking this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions