LSP client permanently stuck in "server is starting" after the server has answered `initialize` (v2.1.207–v2.1.211)
Summary
With a plugin-provided lspServers entry, Claude Code lazily spawns the LSP server on the first LSP tool call. On v2.1.207 — and still on v2.1.211, re-verified 2026-07-16 — if the server answers initialize successfully but not instantly (~140–550 ms in our traces), the client — deterministically for our server shape, 5/5 across both versions — never leaves its "starting" state:
- the triggering LSP tool call hangs indefinitely and Esc does not cancel it (in one session the conversation stopped responding to further prompts entirely);
- concurrent and subsequent LSP tool calls fail immediately with
Cannot send notification to LSP server 'plugin:typemux-cc:typemux-cc': server is starting; - the client never sends
initialized(nor anything else) after receiving theinitializeresponse, for the rest of the session.
What the server saw (server-side trace logs, UTC)
| run | client version | initialize received | response written | client traffic afterwards |
|---|---|---|---|---|
| 1 | 2.1.207 | 00:36:26.904 | 00:36:27.427 (523 ms) | none (observed >5 min) |
| 2 | 2.1.207 | 00:53:51.004 | 00:53:51.553 (549 ms) | none |
| 3 | 2.1.207 | 01:05:49.122 | 01:05:49.263 (141 ms) | none; retried tool calls error client-side without reaching the server |
| 4 | 2.1.211 | 10:58:22.974 | 10:58:23.152 (178 ms) | none; triggering hover hung >90 s (Esc-proof) |
| 5 | 2.1.211 | 11:03:46.335 | 11:03:46.491 (156 ms) | none |
The server stayed healthy throughout each hang (server and its pyright child idle in the process table), and an independent scripted stdio client replaying the identical message flow (initialize → initialized → didOpen → hover) against the same binary completed in 0.46 s.
Reproduction
- LSP server: typemux-cc 0.2.17 (https://github.com/K-dash/typemux-cc), a pyright proxy registered via a plugin's
lspServersmanifest entry. - Project: any repo with
.venvat its root. In this layout the proxy pre-spawns pyright and answersinitializewith pyright's full capabilities after the child handshake (~140–550 ms). - Ask Claude Code to use the LSP tool on any Python file → 5/5 permanent hang as described (3/3 on 2.1.207, 2/2 on 2.1.211).
- Contrast (works, 2/2): same server and repo but started without
.venv— the proxy then answersinitializein <1 ms with{"capabilities": {}}— everything works, including heavy follow-up LSP traffic and mid-session backend spawning. - Additional contrast (works, on 2.1.211): a patched build of the same server that always answers
initializeinstantly with{"capabilities": {}}and defers the pyright child spawn until the first request — same repo,.venvpresent — works fully: the client sentinitialized3 ms after the response, and hover / documentSymbol / cross-file definition / references / publishDiagnostics all succeeded. This narrows the trigger to one (or more) of the three differences listed below.
Differences between the failing and working shapes that we could not discriminate from outside the client:
initializeresponse latency (~141–550 ms vs <1 ms);window/logMessagenotifications emitted by the server ~90 ms after the response, before the client sentinitialized(spec-legal: window notifications are permitted during the initialize phase);- full pyright capabilities vs
{}in the response.
Expected behavior
The client transitions out of "server is starting" once the initialize response arrives, regardless of its latency or of window notifications interleaved after it — and the LSP tool call should be cancellable in any case.
Environment
- Claude Code 2.1.207 (npm publish date 2026-07-10) and 2.1.211 (latest as of 2026-07-16); long-lived sessions started on earlier versions do not show this, exact regression version unknown
- macOS, darwin 25.5.0
- LSP server: Rust binary speaking LSP over stdio, registered via plugin
lspServers