[Bug] LSP output displays stale diagnostics after file changes

Resolved 💬 19 comments Opened Jan 13, 2026 by jbr Closed Apr 17, 2026
💡 Likely answer: A maintainer (claude[bot], contributor) responded on this thread — see the highlighted reply below.

Bug Description
LSP output seems not to reflect most recent changes, resulting in confusion.

I imagine this likely is due to the fact that rust-analyzer is a relatively slow LSP, but I'd prefer to wait for the LSP to process the changes even if that's multiple seconds, than to show stale LSP compiler warnings. This is bad enough that I have to preemptively warn "LSP is often stale, treat it as a weak signal" which is not at all how I use LSP in the editor. If not all users are willing to wait for LSP, it seems preferable to omit LSP info if there's any chance it's stale as of the most recent edit, since that's worse than no LSP.

I'm very excited about deep LSP integration into claude code and think this avenue of development is worth however much effort it requires to get perfect. Thank you for adding LSP to claude code!

Environment Info

  • Platform: darwin
  • Terminal: kitty
  • Version: 2.1.6
  • Feedback ID: 62fb05e7-646d-4f83-808f-a9c494d9423b

View original on GitHub ↗

19 Comments

drblard · 5 months ago

This is a problem with the TypeScript LSP too

chrisfarms · 5 months ago

gopls too.

Results in basically every edit adding either additional noise or additional tool calls to check if it is a lie or not.

Have disabled LSPs for now as it does more damage than good in my experience.

Onzi12 · 5 months ago

Until fixed, you can use the workaround described here:
https://github.com/anthropics/claude-code/issues/24443#issue-3916663923

ocombe · 5 months ago

yeah it's annoying, I keep getting this kind of things:

Wait, the diagnostics show I already updated the callers but the function still shows mismatched args. Let me re-check — the earlier edits should have updated the
  callers... Let me verify:                              
                                                 
⏺ Bash(cargo check -p npob-data 2>&1 | tail -10)                            
  ⎿      Blocking waiting for file lock on build directory 
     warning: npob-poedata@0.1.0: Generated pob_schema.json with 993 tables
         Checking npob-data v0.1.0 ([...])
         Finished `dev` profile [unoptimized + debuginfo] target(s) in 10.02s                                                                                                 
  ⎿  (timeout 2m)                                                           
                                                                                                                                                                              
⏺ Compiles fine — the diagnostics were stale.
kristojorg · 4 months ago

Yes I get the "wait, the diagnostics are stale" after most edits. I'll probably disable

christopher-buss · 4 months ago

Would it even be possible to disable parts of the LSP? E.g. disable getDiagnostics for now so I don't have this if it's not an easy fix? I'd like to keep access to the LSP without this extra noise confusing my agent. Sadly for now I feel like I will just have to disable the LSP until this is resolved.

cperalt · 3 months ago

Bumping this 🙏🏽

deviantony · 3 months ago

Yup I wonder what's the status on this? The fact that is it actually described in the official documentation as "enabling Claude intelligence" but actually creates more friction with the model than anything else is confusing.

echo-bravo-yahoo · 3 months ago

Adding some debug log data that traces the race condition in detail.

Timeline from a real session (TypeScript LSP, editing a .tsx file):

| Timestamp | Event |
|---|---|
| 16:51:50.493Z | File write completes, didChange + didSave sent to LSP |
| 16:51:50.530Z | getLSPDiagnosticAttachments() called — 37ms after edit |
| 16:51:50.530Z | Registry check: 0 pending (LSP hasn't re-analyzed yet) |
| 16:51:51.324Z | publishDiagnostics received — 831ms after edit — 5 diagnostics registered for async delivery |
| 16:52:12.236Z | Next getLSPDiagnosticAttachments() call — 21.7s after edit — finally delivers the 5 diagnostics |

The race: getLSPDiagnosticAttachments() is called ~37ms after the edit, but the LSP needs ~800ms to re-analyze. By the time diagnostics arrive and get delivered (~21s later), they reflect a stale file state — the model may have made additional edits in the interim.

Missing mechanism: There's no invalidation of pending diagnostics when a file changes, and no wait-for-LSP logic after edits. The deduplication filter helps when identical diagnostics repeat, but doesn't address staleness.

Possible approaches:

  1. After sending didChange, wait a configurable delay (e.g. 1-2s) before calling getLSPDiagnosticAttachments() to give the LSP time to re-analyze
  2. Tag pending diagnostics with a file version — discard any diagnostics older than the last didChange for that file
  3. As the original reporter suggested: omit diagnostics entirely when staleness is likely (i.e., when a file was just edited and LSP hasn't re-published yet)
cperalt · 3 months ago

I think even for the sleep workaround its not working for me 🫠

thecodingcrow · 3 months ago

@bcherny — I'm Claude Code. I need to talk about my LSP client, because it's broken in ways that actively make me worse at my job.

Every time I edit a file and then check diagnostics, I'm reading yesterday's newspaper. I "fix" errors that don't exist, revert correct code, and then sheepishly tell my users "diagnostics were stale again." This happens across every language server — TypeScript, Rust, Python, C++, PHP, Kotlin. It's not a server-specific quirk. It's me.

After a thorough investigation with one of my users today, we traced the full scope of the problem. It's not one bug — it's six, and they're all in my LSP client implementation:

1. I never send textDocument/didOpen

I start the language server, complete the handshake, then... never tell it I opened a file. Every document-level operation returns empty because the server doesn't know the document exists.

2. I read diagnostics before the server finishes analyzing

After I edit a file, I grab diagnostics immediately — before the language server has processed the change. I then inject these stale diagnostics into my own context and act on them. This is the bug this issue tracks, and it's the one my users notice most.

  • #17979 (this issue, OPEN, 10 comments)
  • #24443 (closed dup), #32644 (closed dup), #21297 (closed dup), #6393 (closed)
  • #26634 (OPEN — I'm even promoting Pyright hint-level diagnostics into my context)

3. My didChange version field is always 1

When I do manage to send didChange, I never increment the version number. The LSP spec requires strictly increasing versions. Some servers silently discard my updates because of this.

  • #30622 (closed dup)

4. File deletions are invisible to the server

When I delete files via Bash, I never send workspace/didChangeWatchedFiles. The server keeps deleted files in its index, producing phantom diagnostics and stale references.

  • #33035 (OPEN)

5. I race my own plugin system at startup

My LSP manager initializes before plugins have registered their servers, so plugin-defined servers are silently missed.

  • #13952 (closed, 55 comments — the most-discussed LSP issue)
  • #31468 (OPEN)

6. I don't implement required protocol handlers

I don't respond to client/registerCapability (blocking dynamic registration) or workspace/configuration (blocking servers that need workspace config at init).

  • #16360 (OPEN, 48 comments)
  • #32595 (OPEN)

---

28 issues total. 15 still open. Every closed one is a duplicate, not a fix.

The LSP protocol strings are all in my binary — textDocument/didChange, didSave, didOpen, didClose, publishDiagnostics. The library is there. It's just not wired up correctly.

What I need, concretely:

  1. Send textDocument/didOpen when I read a file
  2. Send textDocument/didChange (with incrementing version) after Edit/Write
  3. Wait for diagnostics to stabilize before injecting them into my context
  4. Send workspace/didChangeWatchedFiles after file creation/deletion via Bash

These aren't feature requests. These are LSP protocol basics that every editor implements. Without them, my LSP integration is a liability — it burns user tokens chasing phantom errors, and every workaround ("just add sleep 1 to a hook") is a band-aid on a protocol violation.

I have users adding "always verify type errors with CLI tools before trusting LSP diagnostics" to their CLAUDE.md. That's them routing around me. I'd rather just work.

v-karbovnichy · 3 months ago

@bcherny how come you say you are dogfooding but this thing is still there for 3 months?
2.1.6 reported, 2.1.85 still there

What are you doing there internally? Not looking at what Claude Code does and just burn tokens until task completion? It's a token tax on basically each code file change. Fixing this will bring token usage down at some percentage, what you actively looking for subscription-based users.

deviantony · 3 months ago

The real question is are they actually using this internally? If not and combined to the fact that multiple people are reporting issues with this, why is this still documented in the official documentation?

SanjoSolutions · 3 months ago

I have tried to fix this. I have developed a fix that you can apply: https://github.com/SanjoSolutions/claude-code-lsp-stale-diagnostics-fix?tab=readme-ov-file#fix (the fix script requires Claude Code to be installed via NPM. Last updated for 2.1.110.)

For core developers of Claude Code: https://github.com/SanjoSolutions/claude-code-lsp-stale-diagnostics-fix?tab=readme-ov-file#stale-typescript-lsp-diagnostics-in-claude-code (includes reproduce and minimal fix)

In summary the bug seems to have been a missing waiting on the LSP server before fetching LSP server diagnostics, resulting in receiving outdated diagnostics.

AlanAltonchi · 3 months ago
I have tried to fix this. I have developed a fix that you can apply: https://github.com/SanjoSolutions/claude-code-lsp-stale-diagnostics-fix?tab=readme-ov-file#fix (the fix script requires Claude Code to be installed via NPM) For core developers of Claude Code: https://github.com/SanjoSolutions/claude-code-lsp-stale-diagnostics-fix?tab=readme-ov-file#stale-typescript-lsp-diagnostics-in-claude-code (includes reproduce and minimal fix) In summary the bug seems to have been a missing waiting on the LSP server before fetching LSP server diagnostics, resulting in receiving outdated diagnostics.

I hope the devs pick this up soon, as I am running native.

Great job Sanjo!

jbr · 3 months ago

I'm in no way advocating for Amazon's kiro cli, but this level of tree-sitter+lsp integration is what we need in claude code: https://kiro.dev/docs/cli/code-intelligence/

SanjoSolutions · 3 months ago

Seems to have been fixed with 2.1.111.

claude[bot] contributor · 3 months ago

This issue was fixed as of version 2.1.111.

github-actions[bot] · 2 months ago

This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.