Address the review findings on the previous revision:
- Dispatch resumption on message type as well as metadata: a notification
stamped with a resumption token is POSTed as usual instead of tripping
the resumption path's request-only assertion and killing the write loop.
- Treat any non-2xx as a failure (response.is_success), restoring the
raise_for_status() semantics the checks replaced: an unfollowed redirect
resolves the caller instead of being logged as success.
- Map a 404 on the resumption GET while a session id is held to
INVALID_REQUEST / "Session terminated", the POST path's session-expiry
signal, so reconnect logic keyed on it works across both.
- Contain the resumption read loop like _handle_sse_response: a stream
dying mid-read or ending cleanly without a response resolves the waiter
(CONNECTION_CLOSED) instead of tearing down the transport or hanging.
- Resolve the resumption GET's status errors via _resolve_abandoned_request
for its closed-stream containment instead of hand-building the error.
- Surface network-level errors (httpx.HTTPError) on the SSE message POST
through the same correlated path: on this transport nothing escapes
loudly, so the caller previously hung forever.
- Deduplicate the SSE test app wiring behind make_app(wrap_post=...).
Seven new regression tests pin the above; each fails against the previous
revision (hang into fail_after, transport teardown, or wrong error).
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AuJi8kEB3bhikW2pzbmhUL
Two client-side paths still swallowed non-2xx HTTP responses, leaving the
caller hanging with no way to tell an auth failure from a slow server
(#2110):
- streamable HTTP: a non-2xx on the resumption GET (Last-Event-ID) hit a
bare raise_for_status() inside the request's background task; the
escaping HTTPStatusError tore down the transport's task group and every
stream with it.
- SSE transport: a non-2xx on the message POST raised into post_writer's
catch-all, which logged and dropped it; the waiting caller hung forever
and the write loop died.
Both paths now resolve the waiting request with a JSON-RPC error
correlated to its id, mirroring _handle_post_request's existing non-2xx
handling: the caller gets a prompt INTERNAL_ERROR and the
transport/session stays usable. A non-2xx on a notification POST has no
waiter to resolve, so it is logged and contained.
Regression tests drive both transports in-process (httpx MockTransport /
ASGI) at 401/403/500 and pin that the error is correlated, prompt, and
non-fatal to the session; all fail (hang into fail_after) without the
fix.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AuJi8kEB3bhikW2pzbmhUL