fix(streamable-http): surface transport faults to callers and message_handler

Closed
FU
2mo ago
+130-13

Fixes #1401

What changed

When a streamable-http response stream dies with a transport error (e.g.
httpx.ReadTimeout from sse_read_timeout), the original exception was
swallowed:

  • The pending request was resolved with a generic CONNECTION_CLOSED error
    ("SSE stream ended without a response" / "reconnection attempts were
    exhausted"), hiding the root cause from user code.
  • The fault never reached message_handler, even though the IncomingMessage
    contract says transport-level exceptions are delivered there.

Now:

  • _handle_sse_response captures the stream exception and forwards it into the
    read stream as an Exception item, so the session's message_handler
    observes the fault (custom handlers, like the one the reporter built as a
    workaround, now receive it). The synthesized error keeps code
    CONNECTION_CLOSED and carries the exception in its message.
  • _handle_reconnection carries the latest failure through the retry loop and
    includes it in the exhausted-budget error message.
  • New _forward_stream_fault helper, best-effort like
    _resolve_abandoned_request.

How verified

  • Updated test_a_non_resumable_sse_drop_resolves_the_request_with_an_error
    and test_exhausted_reconnection_attempts_resolve_the_request_with_an_error
    to assert the fault item and the exception in the error message.
  • Added test_an_id_bearing_stream_that_dies_surfaces_the_fault_and_resolves_the_request
    covering the reconnect-exhausted path end to end.
  • pytest tests/client: 703 passed, 8 skipped, 1 xfailed; one unrelated
    pre-existing failure in test_transport_stream_cleanup.py (also fails on
    clean main on this machine). ruff check, ruff format, and pyright clean.

Disclosure: this contribution was written with AI assistance and personally reviewed and tested by me.