Skip to content

Claude Code 2.1.143 silently hangs MCP tool calls after SSE drop — client logs the drop but does not fail or reconnect #60061

Closed as not planned

Description

@arbitermano

Claude Code 2.1.143 silently hangs MCP tool calls after SSE drop — debug logs show client detects the drop but does not fail or reconnect in-flight calls (v3)

TL;DR

When the SSE channel to an MCP server drops, Claude Code logs the drop, then leaves every in-flight tool call running forever. There is no error surfaced to the user, no automatic reconnect, no failure of pending calls — only Esc/user-cancel can break out. Reproduced live with debug logging enabled; exact log lines below.

Severity / Impact

  • A single transient SSE drop on a long-running session makes every MCP tool call hang silently for the rest of the session.
  • Only manual user-cancel or full Claude Code restart recovers.
  • Affects any deployment using SSE-transport MCP servers behind any reverse proxy.

Environment

  • Claude Code: 2.1.143 (cc_version 2.1.143.96b, macOS, Darwin 25.3.0).

  • MCP server: crystaldba/postgres-mcp v0.3.0; image digest sha256:dbbd346860d29f1543e991f30f3284bf4ab5f096d049ecc3426528f20b1b6e6b; mcp Python SDK 1.6.0 inside the container.

  • Reverse proxy: nginx with proxy_buffering off, proxy_cache off, proxy_read_timeout 86400s, proxy_send_timeout 86400s — explicitly configured to support long-lived SSE.

  • Claude Code MCP configuration (~/.claude.json, redacted):

    "pg-mcp": {
      "type": "sse",
      "url": "https://mcp.example.com/sse",
      "headers": { "Authorization": "Bearer <redacted>" }
    }

Reproduction

  1. Configure Claude Code with an MCP server over SSE (config block above).
  2. Make MCP tool calls — they work.
  3. Leave the session idle long enough that the underlying TCP connection / SSE stream is torn down on either side (in our trace: SSE channel had been up for 61937s ≈ 17h12m before the drop event).
  4. Issue any MCP tool call.
  5. Observe: Claude Code dispatches the call, the SSE channel is detected as dropped within a fraction of a second, but the tool call remains "running" indefinitely.

We have not yet pinned down what triggered the SSE drop itself — it could be server-side TaskGroup cancellation noise we see in our mcp SDK 1.6.0 logs (see "Server-side notes"), or container/network state. That is independent of the bug being reported here. The bug is the client's response to a drop it detected on its own.

Observed behavior (verbatim Claude Code debug log, single tool call)

2026-05-17T20:23:00.766Z [INFO]  [Stall] tool_dispatch_start tool=mcp__pg-mcp__list_schemas
                                  toolUseId=toolu_01T2JSzbbHXsXm4jAAdEnuxv permissionDecisionMs=1
2026-05-17T20:23:00.766Z [DEBUG] MCP server "pg-mcp": Calling MCP tool: list_schemas
2026-05-17T20:23:00.767Z [DEBUG] MCP server "pg-mcp": No token data found
2026-05-17T20:23:01.090Z [DEBUG] MCP server "pg-mcp": SSE connection dropped after 61937s uptime
2026-05-17T20:23:01.090Z [DEBUG] MCP server "pg-mcp": Connection error: SSE error: undefined

2026-05-17T20:23:30.768Z [DEBUG] MCP server "pg-mcp": Tool 'list_schemas' still running (30s elapsed)
2026-05-17T20:24:00.769Z [DEBUG] MCP server "pg-mcp": Tool 'list_schemas' still running (60s elapsed)
... (one such line every 30s for 23 minutes, no other MCP events for pg-mcp) ...
2026-05-17T20:46:00.934Z [DEBUG] MCP server "pg-mcp": Tool 'list_schemas' still running (1380s elapsed)

2026-05-17T20:46:12.374Z [DEBUG] [onCancel] source=local focusedInputDialog=undefined streamMode=tool-use
2026-05-17T20:46:12.382Z [DEBUG] MCP server "pg-mcp": Tool 'list_schemas' failed after 1391s:
                                  MCP error -32001: user-cancel
2026-05-17T20:46:12.383Z [WARN]  [Stall] tool_dispatch_end tool=mcp__pg-mcp__list_schemas
                                  toolUseId=toolu_01T2JSzbbHXsXm4jAAdEnuxv outcome=error durationMs=1391617
2026-05-17T20:46:12.384Z [ERROR] McpError: McpError: MCP error -32001: user-cancel

Notes on what this shows:

  • The SSE drop event is logged 324 ms after the tool call is dispatched, with a clear reason string: SSE error: undefined.
  • Between the drop event and the user-cancel 23 minutes later, there are zero MCP-related log lines for pg-mcp other than the 30 s "still running" heartbeat. No reconnect attempt, no error surfaced, no failure of pending toolUseId=toolu_01T2JSzbbHXsXm4jAAdEnuxv.
  • The only thing that ends the call is a local cancel ([onCancel] source=local).

Diagnosed bug (no longer just a hypothesis)

When SSE connection dropped fires for an MCP server, Claude Code 2.1.143 does not:

  1. Fail any in-flight tool calls bound to that server with a transport-level error.
  2. Attempt to reopen the SSE channel.
  3. Surface anything to the user beyond a debug log entry.

Instead it keeps emitting the 30-second "still running" heartbeat forever, while the actual transport is dead.

Alternative root causes considered (and now ruled out as the primary issue)

Alternative Status Notes
nginx idle timeout dropping SSE Ruled out nginx config verified: proxy_read_timeout 86400s, proxy_buffering off.
Bearer-token expiry Ruled out Static tokens, no TTL.
Container restart / OOM Not the primary issue Even if the SSE drop was caused by upstream state, the client behavior on drop is what makes this user-visible.
Multiple Claude Code processes racing Ruled out Server side shows one client connection per process.
Client sent a request before MCP initialize completed Not the primary issue The drop is detected by the client immediately; that is what should trigger fail/reconnect.
MCP Python SDK 1.6.0 server-side session-state bug Possibly contributory Server-side RuntimeError traces are visible in our postgres-mcp logs (see below). They explain why the underlying SSE stream tears down but do not explain why the client then sits silently for 23 minutes.
Normal asyncio/anyio cancellation noise Not the primary issue Same reason.

All of these were live candidates in our earlier draft. The Claude Code debug log eliminates them as the user-visible cause; whatever upstream condition causes the SSE drop, the bug being reported is the client's silent response to that drop.

Suggested mitigation priorities

  1. Primary (Claude Code, client-side) — on SSE connection dropped:
    • Mark all in-flight tool calls for that MCP server as failed with a transport-level error.
    • Either auto-reopen the SSE channel and re-run the MCP initialize handshake, or surface "MCP server disconnected — please retry" to the user.
    • For non-idempotent tools, do not auto-retry — fail the call and let the user decide whether to retry.
  2. Secondary (upstream mcp Python SDK) — replace the bare RuntimeError: Received request before initialization was complete at mcp/server/session.py:163 with a proper MCP error response so future deployments where this is the underlying cause can debug it from client-visible signals.
  3. Operator workaround (verified viable) — switch the MCP server from --transport=sse to --transport=http (Streamable HTTP, added upstream in commit 07eb329, January 2026). Confirmed via claude mcp add --help that Claude Code 2.1.143 supports --transport http. Streamable HTTP is request/response and avoids the dropped-long-lived-channel mode entirely.

Server-side notes (separate from the client bug)

While debug logs were on we captured several instances of the following server-side exception on the same postgres-mcp container. These are likely what is triggering the SSE drops on the server side. Sharing in case the Claude Code team wants to correlate with the client side, but this is upstream mcp SDK behavior, not Claude Code's:

ERROR:    Exception in ASGI application
  + Exception Group Traceback (most recent call last):
  |   File "/app/.venv/lib/python3.12/site-packages/mcp/server/fastmcp/server.py", line 485, in handle_sse
  |   File "/app/.venv/lib/python3.12/site-packages/mcp/server/sse.py", line 123, in connect_sse
  |   File "/app/.venv/lib/python3.12/site-packages/anyio/_backends/_asyncio.py", line 772, in __aexit__
  | ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
  +-+---------------- 1 ----------------
    |   File "/app/.venv/lib/python3.12/site-packages/mcp/shared/session.py", line 324, in _receive_loop
    |   File "/app/.venv/lib/python3.12/site-packages/mcp/server/session.py", line 163, in _received_request
    |     raise RuntimeError(
    | RuntimeError: Received request before initialization was complete

The server returns HTTP 202 Accepted on the POST /messages/?session_id=<id> first, then the ASGI task crashes asynchronously. No MCP-level error frame is sent back. Combined with Claude Code's silent handling of the SSE drop, the net effect on the user is the indefinite hang documented above.

Notes for further investigation

Happy to share on request:

  • Full Claude Code debug log file.
  • Full docker logs output of the MCP container around the drop window.
  • Repeated runs against --transport=http to confirm the workaround removes the hang entirely.

Activity

  1. github-actions commented on May 17, 2026

    @github-actions

    Found 3 possible duplicate issues:

    1. [BUG] Streamable HTTP MCP: in-flight tool call Promise orphaned forever on connection drop #55992
    2. MCP SSE client hangs indefinitely on dropped connection #26368
    3. "Unhandled case: [object Object]" overlay crashes session (correlates with MCP SSE reconnect) #59307

    This issue will be automatically closed as a duplicate in 3 days.

    • If your issue is a duplicate, please close it and 👍 the existing issue instead
    • To prevent auto-closure, add a comment or 👎 this comment

    🤖 Generated with Claude Code

  2. arbitermano commented on May 17, 2026

    @arbitermano
    Author

    Pushing back on the duplicate classification.

    New evidence vs the older #26368: the debug log on 2.1.143 shows

    [Stall] tool_dispatch_start tool=mcp__pg-mcp__list_schemas
    MCP server "pg-mcp": SSE connection dropped after 61937s uptime   (+324 ms after dispatch)
    MCP server "pg-mcp": Tool 'list_schemas' still running (30s elapsed)
    ... (one heartbeat every 30 s for 23 minutes, no other events) ...
    [onCancel] source=local
    MCP error -32001: user-cancel
    

    → no reconnect attempt, no transport-level failure surfaced, only user-cancel ends the call.

    Please keep #60061 open as a v2.1.110-fix regression / incomplete-fix repro on 2.1.143.

  3. kcarriedo commented on May 17, 2026

    @kcarriedo

    Pushing back on the duplicate flag too, and adding evidence that points at where the SSE-side fix may have regressed.

    Running a separate Rust orchestrator that spawns claude -p over MCP-SSE on long-lived sessions (Darwin 25.3.0, 2.1.143). We see the same shape: SSE drop is detected and logged, in-flight tool call never errors, only Esc/process kill recovers. A few observations that might narrow the diff between v2.1.110 and v2.1.143:

    1. The "detected the drop, did not propagate" gap. The fix shipped in v2.1.110 had to cancel pending in-flight calls when the transport went down — your debug log shows the detect side firing (tool_dispatch_start then nothing) but the cancel side never reaching the agent loop. That's the load-bearing detail and matches what we see: the loop is still waiting on a future that has no producer, no error path, no timeout. A regression of just the per-call cancel propagation would look exactly like this while leaving the higher-level reconnect logic intact (which is why this looks "less broken" than #26368 did before the fix).

    2. The 17h12m drop is informative. Long SSE channels are torn down by ~everything between the client and server (nginx keepalives, NAT tables, OS socket timeouts). Even with proxy_read_timeout 86400s and proxy_buffering off, the failure mode you'll hit in production is "TCP RST or FIN, not a graceful close" — and a regression on the in-flight-call cancellation path only manifests on that abrupt-drop case, not the graceful close that's easier to repro on a dev box. That probably also explains why this snuck through whatever regression suite covered v2.1.110: testing graceful close passes, testing socket reset hangs.

    3. Workaround that's holding on our side: wrap MCP tool calls in a parent-side timeout (we use ~5 min for read calls, longer for write) and on timeout, kill+respawn the MCP server subprocess from outside the agent. Ugly but unblocks the session. The proper fix on the Claude Code side is presumably either (a) reinstate the per-call cancel that v2.1.110 added when the transport-down event fires, or (b) add a client-side per-call deadline as a secondary guardrail.

    4. The Streamable-HTTP cousin (#55992) is the right cross-reference, not a duplicate. Same family — "transport detected dead, in-flight calls leak" — but if SSE and HTTP code paths re-converged in 2.1.143 and the cancellation refactor only landed for HTTP, the SSE side regressing alone makes sense. Worth checking whether useUnifiedTransport or equivalent flag flipped recently.

    Happy to share the orchestrator's reconnect-watchdog code if useful as a reference. Independent repro, different workload, same fingerprint.

  4. arbitermano commented on May 18, 2026

    @arbitermano
    Author

    Confirming the fingerprint match. Same OS (Darwin 25.3.0), same 2.1.143, nginx upstream with proxy_read_timeout 86400s — drop happened at the 17h12m mark, no graceful close on either side, so this aligns with your "abrupt RST/FIN, not graceful close" hypothesis explaining why the v2.1.110 regression suite likely missed it.

    Your split of the v2.1.110 fix into (a) transport-down detection and (b) per-call cancel propagation matches my debug log exactly: detection fires (SSE connection dropped + Connection error), nothing else fires, the 30-second heartbeat keeps going. The cancel-propagation side is the load-bearing piece that looks regressed.

    Parent-side per-call deadline as a guardrail is the right secondary fix — agreed it's ugly. Would still want the transport-down → fail-pending-calls path restored as the primary.

    +1 on #55992 being the cross-reference, not a duplicate. If the SSE/HTTP transport refactor diverged again post-v2.1.110, that's the diff to look at.

  5. arbitermano commented on Jun 2, 2026

    @arbitermano
    Author

    Still reproduces on 2.1.160 (latest on npm as of 2026-06-02), same Darwin 25.3.0, same SSE upstream behind nginx with proxy_read_timeout 86400s.

    I walked the changelog across every release from 2.1.143 → 2.1.160 — no entry addresses the load-bearing symptom here (transport-down detected, in-flight call never failed or cancelled). The nearest MCP/SSE touch is:

    • 2.1.153 — "Fixed stateful MCP servers without optional GET SSE stream reconnect-looping on tools/list" — different surface: that's the discovery/tools/list reconnect path looping, not an in-flight tool call hanging on an abrupt drop. In our case there is no reconnect at all.

    Everything else in that range is paginated tools/list (2.1.144/145/147/157), image results (2.1.145), or OAuth/proxy (2.1.152) — none touch the per-call cancel propagation that @kcarriedo and I narrowed this down to.

    So the v2.1.110 regression (transport-down → fail-pending-calls path not firing on SSE) is still live on 2.1.160. Flagging to keep this from going stale / being auto-closed. Happy to re-run the debug-log repro on any candidate build.

  6. mikestarkenburg commented on Jun 12, 2026

    @mikestarkenburg

    Data point that narrows this (tested 2026-06-12 on 2.1.156 and 2.1.175): with a healthy streamable-HTTP MCP server, large tool results alone never hang — 1KB / 64KB / 256KB text payloads all delivered cleanly via claude -p (six runs across both versions, 11–18s each, payloads verified intact).

    So the failure mode here really does look like the orphaned in-flight tool call on transport failure (no client-side timeout), consistent with #38437 — not a payload-size threshold. A per-call client-side timeout for streamable-HTTP (or honoring MCP_TOOL_TIMEOUT in -p mode) would convert the infinite hang into a recoverable tool error.

  7. github-actions commented on Jul 24, 2026

    @github-actions

    Closing for now — inactive for too long. Please open a new issue if this is still relevant.

  8. github-actions commented on Sep 18, 2026

    @github-actions

    This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.

  9. locked as resolved and limited conversation to collaborators on Sep 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:mcpbugSomething isn't workinghas reproHas detailed reproduction stepsplatform:macosIssue specifically occurs on macOSstaleIssue is inactive

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions