Claude Code 2.1.143 silently hangs MCP tool calls after SSE drop — client logs the drop but does not fail or reconnect
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-mcpv0.3.0; image digestsha256:dbbd346860d29f1543e991f30f3284bf4ab5f096d049ecc3426528f20b1b6e6b;mcpPython 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):
``json``
"pg-mcp": {
"type": "sse",
"url": "https://mcp.example.com/sse",
"headers": { "Authorization": "Bearer <redacted>" }
}
Reproduction
- Configure Claude Code with an MCP server over SSE (config block above).
- Make MCP tool calls — they work.
- 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). - Issue any MCP tool call.
- 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-mcpother than the 30 s "still running" heartbeat. No reconnect attempt, no error surfaced, no failure of pendingtoolUseId=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:
- Fail any in-flight tool calls bound to that server with a transport-level error.
- Attempt to reopen the SSE channel.
- 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
- 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
initializehandshake, 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.
- Secondary (upstream
mcpPython SDK) — replace the bareRuntimeError: Received request before initialization was completeatmcp/server/session.py:163with a proper MCP error response so future deployments where this is the underlying cause can debug it from client-visible signals. - Operator workaround (verified viable) — switch the MCP server from
--transport=sseto--transport=http(Streamable HTTP, added upstream in commit07eb329, January 2026). Confirmed viaclaude mcp add --helpthat 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 logsoutput of the MCP container around the drop window. - Repeated runs against
--transport=httpto confirm the workaround removes the hang entirely.
This issue has 6 comments on GitHub. Read the full discussion on GitHub ↗