Skip to content

fix: Reject the retries option with a custom transport - #642

Open
razor-x wants to merge 2 commits into
mainfrom
claude/python-sdk-audit-o2iid9-09-transport-retries
Open

fix: Reject the retries option with a custom transport#642
razor-x wants to merge 2 commits into
mainfrom
claude/python-sdk-audit-o2iid9-09-transport-retries

Conversation

@razor-x

@razor-x razor-x commented Aug 27, 2026

Copy link
Copy Markdown
Member

What

A caller-supplied transport silently disabled retries: Seam(retries=Retry(total=5), httpx_options={"transport": MyTransport()}) performed zero retries with no warning, while env-proxy transports kept them — so retry behavior depended on whether HTTPS_PROXY happened to be set (SDK audit finding M12). Caller-supplied mounts had the same hole.

Following the PHP wave's "an injected client is left untouched" decision (and the injected-client guard the audit praised in the Ruby SDK), the SDK now:

  • Raises SeamInvalidOptionsError when retries= is explicitly passed together with a custom transport or mounts — the caller who brings a transport owns its middleware, and mixing an explicit retry policy with a transport that bypasses it is always a silent lie. Both sync and async clients.
  • Leaves custom transports/mounts untouched and un-retried when retries is not passed (status quo, now documented).
  • Documents the opt-in recipe in the README's retries section: wrap your transport with httpx_retries.RetryTransport.

The client's retries parameter default changes from DEFAULT_RETRIES to None — behaviorally identical (the constructor already coalesced None to the default), but it makes "explicitly passed" well-defined end-to-end. SDK-built transports, including env-proxy mounts, keep their default retry wrapping.

Testing

New test/custom_transport_test.py: the conflict raises with a pinned message (transport and mounts, sync and async); a custom transport without retries makes exactly 1 request against a 503-then-200 server; the documented RetryTransport recipe retries (request count 2, response succeeds).

Revert check: with seam/client.py reverted to main, the conflict test fails with DID NOT RAISE — the audit's silent-acceptance symptom.

Full suite: 190 passed; mypy, pylint (10.00), black, rstcheck clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Y1RzepycXEYA3LStfjt8cY


Generated by Claude Code

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y1RzepycXEYA3LStfjt8cY
@razor-x
razor-x requested a review from a team as a code owner August 27, 2026 21:45
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.

2 participants