Skip to content

fix: prevent infinite retry loop in StreamableHTTP client reconnection#2398

Open
chernistry wants to merge 2 commits intomodelcontextprotocol:mainfrom
chernistry:fix/streamable-http-infinite-retry-2393
Open

fix: prevent infinite retry loop in StreamableHTTP client reconnection#2398
chernistry wants to merge 2 commits intomodelcontextprotocol:mainfrom
chernistry:fix/streamable-http-infinite-retry-2393

Conversation

@chernistry
Copy link
Copy Markdown

@chernistry chernistry commented Apr 6, 2026

Summary

Fix _handle_reconnection() infinite retry loop when a server repeatedly drops connections after partially delivering SSE events (#2393).

Approach: Track whether each reconnection made forward progress (received new SSE events with a different last_event_id). If progress was made, reset the attempt counter — the server is legitimately checkpointing work across disconnections. If no progress was made (same or no new event IDs), increment the counter toward MAX_RECONNECTION_ATTEMPTS to prevent infinite loops.

This preserves the existing multi-reconnection behavior tested by test_streamable_http_multiple_reconnections while preventing the infinite loop in #2393.

Fixes #2393

Details

The bug is on this line: when the SSE stream ends normally (iterator exhausted, no exception), _handle_reconnection calls itself recursively with attempt=0. This means every connection that drops before delivering a response/error resets the counter, and MAX_RECONNECTION_ATTEMPTS is never reached.

The fix compares reconnect_last_event_id against the incoming last_event_id:

  • Different → new events were delivered → reset counter (legitimate checkpoint-and-reconnect)
  • Same → no new data → increment counter → eventually give up

Test plan

  • New test test_handle_reconnection_does_not_retry_infinitely uses a static event ID (no progress) and verifies termination within MAX_RECONNECTION_ATTEMPTS
  • Existing test_streamable_http_multiple_reconnections continues to pass — it delivers new event IDs on each reconnection (progress), so the counter resets as expected
  • Conformance tests pass

🤖 Generated with Claude Code

The `_handle_reconnection` method reset its attempt counter to 0 when a
stream ended normally (without exception), causing an infinite retry loop
when the server repeatedly dropped connections after partially delivering
events. This is because the recursive call on the "stream ended without
response" path passed `attempt=0` instead of incrementing.

Change the normal-close reconnection path to pass `attempt + 1`, matching
the exception path, so total reconnection attempts are tracked across
the entire chain regardless of how each individual stream ended.

Fixes modelcontextprotocol#2393

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@chernistry chernistry force-pushed the fix/streamable-http-infinite-retry-2393 branch from be86955 to 9630947 Compare April 6, 2026 14:50
The previous fix (attempt + 1 unconditionally) broke legitimate
multi-reconnection scenarios where the server checkpoints progress
between disconnections. Now we check whether new SSE events were
delivered (last_event_id changed) — if so, reset the counter; if not,
increment it toward MAX_RECONNECTION_ATTEMPTS.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.

StreamableHTTP client: _handle_reconnection resets attempt counter to 0, causing infinite retry loop

1 participant