Repository navigation
streamable-http-client: RequestHandle::cancel hangs before response stream starts #1193
Description
Describe the bug
With protocol version 2026-07-28 over Streamable HTTP, RequestHandle::cancel() can hang while the original request POST is waiting for its first response event.
The client worker awaits post_message_with_max_sse_event_size(...) inline. While that future is pending, the worker cannot process the cancellation message that should close the request's HTTP/SSE response path. A long-running tool that emits no events therefore cannot be cancelled through RMCP's public client API.
This reproduces on current main (6f8dcde, rmcp 3.1.3).
To Reproduce
- Start a stateless Streamable HTTP server using protocol version
2026-07-28. - Make its
tools/callhandler wait forRequestContext::ctwithout returning a response or progress event. - Connect with
StreamableHttpClientTransportandClientLifecycleMode::Discover. - Start the call with
send_cancellable_request(...). - After the handler starts, call
RequestHandle::cancel(...)inside a five-second timeout.
The cancel call times out:
RequestHandle::cancel should not wait for the response stream: Elapsed(())
The regression test in the proposed PR exercises this exact flow using RMCP on both sides.
Expected behavior
RequestHandle::cancel() should return promptly. RMCP should abort the pending request POST, closing that request's response path so the server cancels the handler and fires RequestContext::ct.
This is the required behavior for the modern protocol:
- MCP 2026-07-28 transport overview: Streamable HTTP cancellation closes the request's response stream.
- MCP 2026-07-28 Streamable HTTP: the server must treat that close as cancellation and stop work as soon as practical.
Logs
No transport error is logged. The cancellation future remains pending until the test timeout.
Additional context
The modern client path already converts a cancellation message into cancellation of a per-request CancellationToken, so this appears to be an ordering bug rather than an intentional limitation. The token is currently registered only after the POST produces an SSE response, which is too late to cancel a POST still waiting for its first response event.
Issues #857 and PR #967 fixed the matching server-side disconnect handling. This issue is the client-side path needed to trigger that behavior through RequestHandle::cancel().
Activity
LizunovSergey commentedon Sep 12, 2026
This looks fixed on current
main(b86995a) — by #1186, which merged on 2026-08-24, five days after this was filed. That would explain why it is still open with theneeds confirmationlabel.The ordering the issue describes has been inverted.
Worker::post_requestnow takes the request's cancellation token before issuing the POST and races the two:So the token is no longer "registered only after the POST produces an SSE response" — a POST still waiting for its first response event is cancellable.
Check I ran. Using the repo's own scripted-transport harness in
tests/test_streamable_http_client_concurrency.rs, which lets the test hold a request POST pending, withClientLifecycleMode::Discover { preferred_versions: vec![ProtocolVersion::V_2026_07_28] }to match the conditions here:That passes —
cancelreturns immediately instead of timing out after five seconds.Two caveats, so this is confirmation rather than proof: it drives a scripted
StreamableHttpClientrather than a live Axum listener, so it pins the client-worker ordering (which is where the issue located the bug) and not the server's reaction to the closed stream; and I did not test against the@webclaw/mcp-style setup in the original repro.Worth noting that no existing test names this scenario — the closest are
cancellation_still_runs_while_recovery_waits_for_old_postsandcancellation_bypasses_queued_posts_at_capacity, both of which pin cancellation against other traffic rather than against the request's own pending POST. If you would like the case above locked in as a regression test so it cannot silently return, I am happy to send that as a small PR — say the word and I will open one.