typescript-lsp: tsserver spawned with no memory cap (one per session) — runaway instance reached 61.5 GB
Summary
The official typescript-lsp plugin starts typescript-language-server, which in turn spawnstsserver.js without any memory limit. On a large TypeScript monorepo each instance sits at
2–4 GB steady state, one instance is started per Claude Code session (they are not shared
between sessions, even for the identical project root), and there is no user-facing setting to
cap them.
On 2026-08-16 one of these instances grew to 61.5 GB of commit charge over 6.3 hours, exhausted
the machine's 123 GB system commit limit, and the resulting kernel pool allocation failures caused a
bugcheck. This is confirmed from a full kernel dump, not inferred.
I am not claiming Claude Code caused the bugcheck — the driver that actually crashed (a third-party
filesystem filter) failed to null-check a failed pool allocation, which is its own defect and is
being reported to that vendor separately. But the memory exhaustion that triggered it came fromtsserver.js, and that part is fixable here.
Environment
| | |
|---|---|
| Claude Code | 2.1.233 |
| Plugin | typescript-lsp@claude-plugins-official 1.0.0 |
| OS | Windows 11 Pro 26200 (25H2) |
| RAM / commit limit | 63.8 GB physical / 123 GB commit limit |
| Node (for tsserver) | 22.12.0 (via Volta) |
| Repo | pnpm/nx monorepo, ~4 apps, large generated Prisma + nestia SDK types |
What is actually launched
Process tree observed live (Win32_Process, parent chain), 6 levels deep:
claude.exe
└ cmd.exe /c typescript-language-server.cmd "--stdio"
└ volta.exe volta run typescript-language-server "--stdio"
└ cmd.exe
└ node.exe .../typescript-language-server/lib/cli.mjs --stdio
└ node.exe .../node_modules/typescript/lib/tsserver.js
--useInferredProjectPerProjectRoot
--cancellationPipeName ...\tscancellation*
--locale en --validateDefaultNpmLocation --useNodeIpc
Note what is absent: no --max-old-space-size on the tsserver command line, and NODE_OPTIONS
is empty. So tsserver runs with V8's default heap sizing and nothing bounds it.
Why the client has to fix this
typescript-language-server does not accept a memory limit on the command line. Its entire CLI
surface is:
-V, --version
--stdio
--log-level <log-level>
-h, --help
The limit is only reachable through the LSP initialize request:
initializationOptions.maxTsServerMemory — "set the maximum size of the V8's old memory section in megabytes" — https://github.com/typescript-language-server/typescript-language-server/blob/master/docs/configuration.md
So only the LSP client can set it. Since Claude Code does not send maxTsServerMemory, there is
no way for a user to bound this — not via plugin config, not via a wrapper script, not via CLI flags.
(For comparison, VS Code defaults typescript.tsserver.maxTsServerMemory to 3072 MB and passes--max-old-space-size through.)
Measurements
Live, on the machine right now — full-semantic instances owned by Claude Code sessions
(project roots anonymized):
| Private commit | Project root |
|---:|---|
| 4,181 MB | repo-A |
| 3,109 MB | repo-A/.claude/worktrees/<worktree> |
| 2,576 MB | repo-A ← same root as row 1, different session |
| 469 MB | repo-B |
The partialSemantic companions are fine (81–110 MB each). It is the full-semantic server that grows.
Two things compound here:
- No cap. Nothing stops a single instance from growing without bound.
- No sharing. Two sessions open on the identical project root run two independent
full-semantic servers — 4,181 MB + 2,576 MB = 6.6 GB for one repo. With N sessions the cost is
N × (2–4 GB). Nine sessions were open at crash time.
Evidence from the crash
From the kernel dump (cdb -z MEMORY.DMP, !vm 0):
********** Number of committed pages is near limit ********
********** 11209 commit requests have failed **********
********** 5265 pool allocations have failed **********
Committed pages: 32321457 ( 129285828 Kb)
Commit limit: 32321457 ( 129285828 Kb) <- 100%
Pid ImageName Commit
1060c node.exe 64482844 Kb <- 61.50 GB, single process
5320 WindowsTerminal.exe 6164840 Kb
a2bc node.exe 4165108 Kb
5fd0 node.exe 4046356 Kb
Rebuilding the parent chains from the same dump, the top three node.exe consumers are all the
same 6-level shape — node ← node ← cmd ← volta ← cmd ← claude.exe — one per Claude Code session:
| Commit | Owning session |
|---:|---|
| 61.50 GB | claude.exe (0xa994) |
| 3.97 GB | claude.exe (0x71ac) |
| 3.86 GB | claude.exe (0x5cf8) |
The two 3.9 GB figures match today's live steady-state measurements exactly, which is what identifies
the 61.5 GB process as the same component in a runaway state rather than something unrelated.
Process creation times (dt nt!_EPROCESS <addr> CreateTime) put the whole chain at
11:43:46.5–47.6, and the crash at 18:03:36 — so the instance grew to 61.5 GB in 6h 20m.
The kernel dump contains no user-mode memory, so the crash-time command line itself is not readable;
the identification rests on the exact 6-level chain shape plus the live command-line capture of the
identical chain.
Ruled out
I originally suspected orphaned hook processes (a known issue in a plugin I use). That is wrong, and
the instrumentation disproves it:
- The two PIDs do not appear anywhere in that day's 20 MB hook trace (matches on other days are PID reuse).
- Hook processes on this machine all run under fnm's node; there is no
volta.exein their chain. - The one hook that did time out in that window (11:43:43) was reaped and exited 1.3 s later, logged.
- Scanning all 824 session transcripts, zero tool calls in that 60-second window spawn node.
Expected behaviour
- Send
initializationOptions.maxTsServerMemoryoninitialize, with a sane default
(VS Code uses 3072 MB) and a user-overridable setting.
- Consider reusing one language server per project root across sessions instead of one per session,
or at least document the per-session cost.
- Surface it: when a language server exceeds its cap it restarts and loses state, so a log line
beats silent degradation.
Item 1 alone would have bounded this at ~3 GB instead of 61.5 GB and the machine would not have gone down.
Workarounds available today
- Close idle Claude Code sessions (each one is worth 2–4 GB).
- Disable the plugin:
"typescript-lsp@claude-plugins-official": falsein~/.claude/settings.json.
Neither is a fix — the first is manual and the second gives up the feature.