Skip to content

fix(streaming): wrap mid-stream transport errors as APIError subclasses - #1974

Open
intertermux-code wants to merge 2 commits into
anthropics:mainfrom
intertermux-code:fix/1919-stream-transport-errors
Open

intertermux-code wants to merge 2 commits into
anthropics:mainfrom
intertermux-code:fix/1919-stream-transport-errors

Conversation

@intertermux-code

Copy link
Copy Markdown

Fixes #1919.

The initial send wraps transport failures (httpx2.TimeoutException → APITimeoutError, other transport errors → APIConnectionError), but once the response was streaming, _streaming.py iterated it with no handling — a read timeout or dropped connection mid-stream surfaced as the raw httpx exception, outside the APIError hierarchy and outside max_retries.

Stream._iter_events and AsyncStream._iter_events now apply the same translation as the initial send, with the same passthrough for SDK-originated errors. Added sync/async regression tests using a mock transport that dies mid-stream.

_streaming.py iterated the response with no error handling, so a read
timeout or dropped connection mid-stream escaped as a raw httpx2
exception. Translate it the same way the initial send does:
httpx2.TimeoutException -> APITimeoutError, other transport errors ->
APIConnectionError, leaving SDK-originated errors untouched.

Fixes anthropics#1919.
@intertermux-code
intertermux-code requested a review from a team as a code owner October 5, 2026 05:51
@sigley

sigley commented Oct 7, 2026

Copy link
Copy Markdown

Two things look worth resolving before this lands.

First, this is the same open issue already covered by #1920 and #1922. #1919 itself points to both of them, and both are still open. #1920 in particular changes these same two _iter_events methods and already scopes the non-timeout handler to httpx2.TransportError. Unless this PR intentionally differs, it would be better to coordinate on one branch rather than leave a third competing fix for the same two hunks.

Second, this head is broader than those earlier fixes:

except Exception as err:
    if isinstance(err, AnthropicError):
        raise
    raise APIConnectionError(...)

That try covers both response.iter_bytes() and the SSE byte decoder. With the default decoder, for example, an invalid UTF-8 SSE line raises UnicodeDecodeError at raw_line.decode("utf-8"); on this branch that becomes APIConnectionError, even though it is not an httpx2 transport failure. A custom SSEBytesDecoder raising its own ordinary exception is relabeled the same way. #1920/#1922 deliberately use except httpx2.TransportError so decoder/parsing failures keep their original identity.

Could the second handler be narrowed to httpx2.TransportError and a non-transport decoder-error regression be added? That would restore the exception hierarchy for connection failures without turning decoder bugs or malformed stream data into network errors.

…rrors

The broad `except Exception` in `_iter_events` also caught decoder failures,
so e.g. a `UnicodeDecodeError` from a malformed SSE line (or an exception from
a custom SSEBytesDecoder) was relabeled as `APIConnectionError`. Scope the
non-timeout handler to `httpx2.TransportError` so decoder errors keep their
original identity, and add a regression test for it.
@intertermux-code

Copy link
Copy Markdown
Author

Done — narrowed both _iter_events handlers to except httpx2.TransportError, which brings this PR's scope exactly in line with #1920. You were right about the broad except Exception: it also covered the SSE decoder, so a UnicodeDecodeError from a malformed SSE line (or an exception from a custom SSEBytesDecoder) would have been relabeled as APIConnectionError.

Also added test_mid_stream_decoder_error_keeps_original_identity (sync + async) asserting that decoder errors keep their original identity — it fails against the old handler and passes now. The transport-error wrapping tests still pass unchanged.

On the duplication point: noted. #1920 has been untouched for a month, so I'll keep this branch live as the active one — happy to consolidate onto #1920 instead if you'd rather.

This branch has not been deployed

No deployments
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.

Transport errors while consuming a stream escape as raw httpx exceptions instead of APITimeoutError / APIConnectionError

2 participants