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