You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Transport errors while consuming a stream escape as raw httpx exceptions instead of APITimeoutError / APIConnectionError #1919
_base_client wraps the initial send: httpx.TimeoutException becomes APITimeoutError, other transport errors become APIConnectionError, and both go through the retry loop (_base_client.py around lines 1291-1300 on 1.4.0). Once the response is streaming, _streaming.py iterates the response with no handling at all, so a read timeout or a dropped connection mid-stream surfaces as the raw httpx.ReadTimeout / httpx.RemoteProtocolError (httpx2.* on 1.x). That is not an APIError, and max_retries is not consulted.
So except anthropic.APIError around a streaming call misses the most common streaming failure, and a client with max_retries=2 makes exactly one request.
Offline repro, no network. The mock transport yields one event and then raises ReadTimeout:
anthropic 0.122.0, httpx 0.28.1
escaped: httpx.ReadTimeout: timed out while reading the stream
isinstance(e, anthropic.APIError) = False
requests made with max_retries=2: 1
anthropic 1.4.0, httpx 2.12.0
escaped: httpx2.ReadTimeout: timed out while reading the stream
isinstance(e, anthropic.APIError) = False
requests made with max_retries=2: 1
Expected: the same wrapping as the initial send, APITimeoutError / APIConnectionError, so the exception hierarchy holds for the whole call. Whether a partially consumed stream can be retried is a separate question, since the caller has already seen events; wrapping alone fixes the catch side. openai-python has the same gap, same generated base: openai/openai-python#3811
For reference, openai-python shipped the same fix in 3.14.0 (openai/openai-python#3827): it wraps the byte iteration in Stream._iter_events / AsyncStream._iter_events and maps timeouts to APITimeoutError and other transport errors to APIConnectionError, chained with from err. Both open PRs here do the same mapping: #1920 at the same spot as #3827, and #1922 around all of __stream__, with more tests (sync and async ReadError, and a parse error that must not come out as APIConnectionError). Either one covers what I reported here.
Activity
HardMax71 commentedon Sep 25, 2026
For reference, openai-python shipped the same fix in 3.14.0 (openai/openai-python#3827): it wraps the byte iteration in
Stream._iter_events/AsyncStream._iter_eventsand maps timeouts toAPITimeoutErrorand other transport errors toAPIConnectionError, chained withfrom err. Both open PRs here do the same mapping: #1920 at the same spot as #3827, and #1922 around all of__stream__, with more tests (sync and asyncReadError, and a parse error that must not come out asAPIConnectionError). Either one covers what I reported here.fix(streaming): wrap mid-stream transport errors as APIError subclasses