SIGN IN SIGN UP

fix(client): retry only replayable request content (#3771)

- [x] I understand that this repository is auto-generated and my pull
request may not be merged

## Changes being requested

Automatic retries can reuse a consumed request body. A one-shot iterable
or non-seekable multipart file part may then send an empty body on the
retry; if that retry succeeds, the SDK reports success without resending
the original payload. Seekable streams also need their original offset
restored. The expected behavior is to retry only when every body part
can be replayed.

The retry paths currently decide whether to retry based on response
status or request failure, but do not check the replayability of
`options.content` and `options.files` or rewind seekable parts. This
patch adds one replayability guard for both synchronous and asynchronous
clients:

- bytes, bytearrays, and exact list/tuple containers remain retryable;
- opaque iterables, including wrappers that return one stored iterator,
and non-seekable streams are treated as non-replayable;
- seekable content and multipart file parts are restored to their
starting offset before retrying;
- retryable status responses, timeouts, and connection failures all use
the guard.

The patch does not buffer request bodies or change the public API.
[#3484](https://github.com/openai/openai-python/pull/3484) proposes
spooling non-seekable multipart streams to preserve retries; this PR
takes the smaller, conservative approach of not retrying a consumed
stream. The multipart checks here were added in response to review
feedback, so they are no longer out of scope.

## Additional context & links

The one-shot-content and multipart regression cases fail on the earlier
base and pass with this change. The branch has been normally merged with
current `main`, retaining the separate retry-limit and application-error
changes from [#3867](https://github.com/openai/openai-python/pull/3867).

Validation after that merge:

- `PYTHONPATH=src ./scripts/test tests/test_client.py` with the
repository's pinned Steady mock server: 275 passed, 2 skipped in each
Pydantic lane;
- `PYTHONPATH=src ./scripts/lint`: Ruff, Pyright, mypy, and import
checks passed using the documented Node.js and pnpm versions;
- `git diff --check`: passed.

---------

Co-authored-by: Marcus Wood <marcuswood@openai.com>
H
HughChaw committed
6e0c2beeb639e42ff74f14135151d8bcb1e5b245
Parent: b56e6d8
Committed by GitHub <noreply@github.com> on 9/22/2026, 7:08:34 PM