claude-code - 💡(How to fix) Fix [BUG] LSP client never sends textDocument/didClose, leaking language-server resources until OOM

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Error Message

No crash or stack trace — the symptom is unbounded resource growth. Representative ps output as documents accumulate (a Lean server here, but the shape is server-agnostic):

$ ps -o pid,rss,command -ax | grep 'lean --worker' | grep -cv grep 51 $ ps -o pid,rss,command -ax | grep 'lean --worker' | grep -v grep 60123 2113344 .../bin/lean --worker file:///proj/A.lean 60291 1987776 .../bin/lean --worker file:///proj/B.lean ... (49 more, none ever removed) ...

one process per file ever opened; total RSS ~100 GB; heavy swapping (100k+ pageouts)

Fix Action

Fix / Workaround

  • Why it reached ~100 GB with Lean: each Lean worker can hold ~2 GB resident (more on a cold build, where it elaborates from source instead of loading cached artifacts), so ~50 never-closed documents added up fast. Lighter language servers would show the same unbounded-growth shape at a smaller scale.
  • A Lean-specific stopgap exists — the lean-lsp-plugin now wraps the server in a process-reaping supervisor (https://github.com/jcreinhold/lean-lsp-plugin) — but a workaround can't see didOpen/didClose and has to guess which documents are idle. The general fix is client-side document lifecycle (send didClose, or evict idle documents), which would help every language, not just Lean.

Code Example

No crash or stack trace — the symptom is unbounded resource growth. Representative `ps` output as documents accumulate (a Lean server here, but the shape is server-agnostic):


$ ps -o pid,rss,command -ax | grep 'lean --worker' | grep -cv grep
51
$ ps -o pid,rss,command -ax | grep 'lean --worker' | grep -v grep
 60123 2113344 .../bin/lean --worker file:///proj/A.lean
 60291 1987776 .../bin/lean --worker file:///proj/B.lean
 ... (49 more, none ever removed) ...
# one process per file ever opened; total RSS ~100 GB; heavy swapping (100k+ pageouts)
RAW_BUFFERClick to expand / collapse

Preflight Checklist

  • I have searched existing issues and this hasn't been reported yet
  • This is a single bug report (please file separate reports for different bugs)
  • I am using the latest version of Claude Code

What's Wrong?

Claude Code's LSP client opens documents (textDocument/didOpen) as it touches files but never sends textDocument/didClose when it is done with them, and it never evicts idle documents. Any language server that allocates per-open-document resources (commonly one worker process per file) therefore grows without bound for the lifetime of the session.

I hit this with a Lean language server (lake serve, see lean-lsp-plugin): as Claude navigated across files, leaked worker processes climbed to 51 alive at once and a transient ~100 GB resident set with heavy swapping that degraded the whole machine. The count only ever grew — no file was ever released. The defect is in the client's document lifecycle, not in the server; any per-document language server should be affected.

What Should Happen?

A language server can only release a document's resources once the client tells it the document is closed. So Claude Code should bound the documents it holds open, via any of:

  • send textDocument/didClose for documents it has finished with; or
  • evict idle open documents after a threshold (close the least-recently-used ones); or
  • expose a configurable cap on concurrently open LSP documents.

Error Messages/Logs

No crash or stack trace — the symptom is unbounded resource growth. Representative `ps` output as documents accumulate (a Lean server here, but the shape is server-agnostic):


$ ps -o pid,rss,command -ax | grep 'lean --worker' | grep -cv grep
51
$ ps -o pid,rss,command -ax | grep 'lean --worker' | grep -v grep
 60123 2113344 .../bin/lean --worker file:///proj/A.lean
 60291 1987776 .../bin/lean --worker file:///proj/B.lean
 ... (49 more, none ever removed) ...
# one process per file ever opened; total RSS ~100 GB; heavy swapping (100k+ pageouts)

Steps to Reproduce

Any language server that spawns a process per open document reproduces this; Lean just makes each leaked document expensive, so the impact is obvious.

  1. Install a Lean LSP plugin and launch Claude Code from a large Lean + mathlib project root. For the lean-lsp-plugin: claude plugin marketplace add jcreinhold/lean-lsp-plugin then claude plugin install lean-lsp-plugin@lean-lsp-plugin.
  2. Have Claude navigate across many .lean files — hover / go-to-definition / find-references over dozens of files.
  3. In another terminal, watch the per-document server processes: watch -n5 'ps -o pid,rss,command -ax | grep "lean --worker" | grep -v grep'
  4. Observe the process count grow monotonically and never shrink, even long after each file is no longer in use, and total RSS climb into the tens of GB. No textDocument/didClose is sent for the navigated files.

Claude Model

Not sure / Multiple models

Is this a regression?

I don't know

Last Working Version

No response

Claude Code Version

2.1.158

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

Other

Additional Information

  • Why it reached ~100 GB with Lean: each Lean worker can hold ~2 GB resident (more on a cold build, where it elaborates from source instead of loading cached artifacts), so ~50 never-closed documents added up fast. Lighter language servers would show the same unbounded-growth shape at a smaller scale.
  • A Lean-specific stopgap exists — the lean-lsp-plugin now wraps the server in a process-reaping supervisor (https://github.com/jcreinhold/lean-lsp-plugin) — but a workaround can't see didOpen/didClose and has to guess which documents are idle. The general fix is client-side document lifecycle (send didClose, or evict idle documents), which would help every language, not just Lean.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

claude-code - 💡(How to fix) Fix [BUG] LSP client never sends textDocument/didClose, leaking language-server resources until OOM