[Bug] TypeScript LSP server wedges silently, returning empty results instead of errors and never recovers after crash

Status Open
Reported on v2.1.220
Maintainer reply None cached
Activity 0 comments · opened Jul 29, 2026

typescript-lsp plugin: wedged language server returns empty results instead of errors, and never recovers

Summary

The typescript-lsp@claude-plugins-official plugin can reach a state where its typescript-language-server is alive but has never loaded a project. In that state every LSP operation returns an empty result rather than an error, which is indistinguishable from a legitimate "this symbol has no references". There is no liveness check, no recovery, and no way to reset it short of restarting Claude Code.

The empty-vs-error confusion is the serious part. An agent asked to remove an exported symbol runs findReferences, receives "No references found", and reasonably concludes the symbol is unused. In my case the symbol had 11 call sites across 5 packages. A silent false negative on a reference lookup is the worst possible failure shape for this tool, because it actively licenses the destructive edit it was supposed to prevent.

Environment

  • Claude Code 2.1.220
  • Plugin typescript-lsp@claude-plugins-official 1.0.0 (installed 2026-07-27)
  • macOS 26.5, arm64
  • node v24.13.0, typescript-language-server 5.3.0, global typescript 5.9.3
  • Large pnpm + turborepo monorepo (~11.8k files, TS project references, also uses @typescript/native-preview / tsgo)

Symptom 1 — every operation returns empty

findReferences, hover and documentSymbol all returned empty for every file tried, including an 8-line file inside the workspace and a 12-line standalone .ts file:

No symbols found in document. This may occur if the file is empty, not supported
by the LSP server, or if the server has not fully indexed the file.

The files were non-empty, .ts, and inside the server's cwd. Nothing distinguishes this message from a genuine empty result.

The wedged servers

Two typescript-language-server processes had been running for ~24 hours:

  PID  PPID %CPU  RSS      ELAPSED COMMAND
25158 24175  0.0  17456 01-00:13:44 node .../bin/typescript-language-server --stdio
99912 85429  0.0   1088 01-01:46:05 node .../bin/typescript-language-server --stdio
99914 99912  0.0   1088 01-01:46:05 .../tsserver.js --serverMode partialSemantic --useInferredProjectPerProjectRoot ...
99915 99912  0.0   1104 01-01:46:05 .../tsserver.js --useInferredProjectPerProjectRoot ...

~1 MB RSS is the tell. A server holding a real TypeScript project for this repo sits in the hundreds of MB (a healthy tsgo --lsp alongside it was at 317 MB). These had 0% CPU for a day and had clearly never loaded a project, yet the plugin kept routing requests to them.

Both PPIDs (24175, 85429) were live claude processes, so this is not an orphaned-process problem — the servers wedged while their owning sessions were still running, and were never health-checked afterwards.

SIGTERM did not kill 99912; it required SIGKILL, consistent with being hung.

Symptom 2 — no recovery after the process dies

After killing the wedged servers, no typescript-language-server process existed at all, but every LSP request then failed with:

Error performing documentSymbol: Cannot send request to LSP server
'plugin:typescript-lsp:typescript': server is running

So the plugin's manager still believed a server was running, refused to send requests, and never respawned. Verified with pgrep -f typescript-language-server returning nothing while the error persisted. Only a full Claude Code restart recovers.

Also observed

.in_use markers accumulate under the plugin's install path, one per session:

~/.claude/plugins/cache/claude-plugins-official/typescript-lsp/1.0.0/.in_use/{24175,85429,85867}

Steps to reproduce

I don't have a deterministic trigger; the wedge appeared after sessions had been open for ~24h against a large monorepo. What is reliably reproducible is Symptom 2:

  1. With the plugin active, use any LSP operation so a server spawns.
  2. kill -9 the typescript-language-server process.
  3. Any further LSP call returns … server is running forever, with no process alive and no respawn.

Requests, in priority order

  1. Never return an empty result for a transport or server failure. Distinguish "the server answered with zero results" from "the server did not answer". This alone would have turned a silently wrong refactor into a visible error.
  2. Respawn when the tracked process is gone. Check the PID before rejecting a request with server is running.
  3. Health-check before reuse. A server that fails to respond to initialize, or that has no project loaded, should be recycled rather than kept indefinitely.
  4. Surface server state to the user — something like /lsp status showing pid, project root, and whether a project is loaded. Diagnosing this took a ps/RSS investigation that no ordinary user would attempt.

View original on GitHub ↗