is it normal for lsp-mode to show stale diagnostics until I modify/save the file?
perhaps 🤔 I think I have file watchers enabled and the log lists them at startup. When I modify buffer B in my example, I can look at the lsp io log and see a textDocument/didChange event and a bunch of other events. But those seem to affect only buffer B and not buffer A, even if A is visible.
lsp-file-watchers is for when file is changed outside the editor when the file is/was not opened, so from your steps I'd say clojure-lsp should send the diagnostics update and we do have a logic for that but there is a high chance it's a flychceck issue updating it in emacs
the best way to debug is enable lsp-log-io , do the steps and see if clojure-lsp is sending it properly, then we would debug on emacs side
I figured out what it was: :notify-references-on-file-change was false.
I had previously altered that because I had performance issues (https://clojurians.slack.com/archives/CPABC1H61/p1756474783539759?thread_ts=1756213182.089459&cid=CPABC1H61) when switching branches and setting that to false helped a lot.
unfortunately it seems the performance issues are still there. So
• :notify-references-on-file-change is false: buffers may show stale data, but switching branches is fast
• :notify-references-on-file-change is true: other buffers are (immediately) updated when changes occur elsewhere and code lenses etc. are (always) up-to-date. But switching git branches causes my cpu usage to go 800-1000% (= several cpu cores hitting 100%) for a while (maybe a minute or two, much longer than opening the project with a hot cache), even if the branches only differ slightly.
Interesting, it's been a while nobody complains about performance in that, is your project huge?
just to be clear: I'm not complaining, merely providing a point of data 🙂
sure :)
but yes, this is a relatively largeish project; about 2k namespaces and 300+ k loc.
wow , yeah, we can probably tweak that
I had no memory we already discussed this years ago https://github.com/clojure-lsp/clojure-lsp/issues/1205 🙂
me too haha
it did a brief test, where I made a tiny change to a common utilitity namespace and then switched branches to the previous commit. Lots of cpu usage for a while, after which the logs were filled with several thousands of lines of :lsp/publish-diagnostics 0ms. I only had like 2-3 files open in emacs.
I could also spot a few interesting lines:
• [clojure-lsp.feature.file-management:157] - :reference-files/analyze 47169ms
• [clojure-lsp.feature.file-management:151] - :internal/notify-references 48438ms
also, the following pattern repeated maybe 5 times
2026-03-17T12:20:15.405Z INFO [clojure-lsp.server:133] - :lsp/publish-diagnostics 0ms
2026-03-17T12:20:15.405Z INFO [clojure-lsp.server:133] - :lsp/publish-diagnostics 0ms
2026-03-17T12:20:15.405Z INFO [clojure-lsp.server:133] - :lsp/publish-diagnostics 0ms
2026-03-17T12:20:15.406Z INFO [clojure-lsp.server:133] - :lsp/publish-diagnostics 0ms
2026-03-17T12:20:15.406Z INFO [clojure-lsp.feature.file-management:151] - :internal/notify-references 48735ms
2026-03-17T12:20:15.406Z INFO [clojure-lsp.server:133] - :lsp/publish-diagnostics 0ms
2026-03-17T12:20:15.589Z INFO [clojure-lsp.handlers:443] - :lsp/code-lens 1ms
2026-03-17T12:20:15.635Z INFO [clojure-lsp.handlers:450] - :lsp/code-lens-resolve 30ms
2026-03-17T12:20:15.635Z INFO [clojure-lsp.handlers:450] - :lsp/code-lens-resolve 30ms
2026-03-17T12:20:15.640Z INFO [clojure-lsp.handlers:450] - :lsp/code-lens-resolve 35ms
2026-03-17T12:20:15.648Z INFO [clojure-lsp.handlers:450] - :lsp/code-lens-resolve 43ms
2026-03-17T12:20:15.648Z INFO [clojure-lsp.handlers:450] - :lsp/code-lens-resolve 43ms
2026-03-17T12:20:15.673Z INFO [clojure-lsp.handlers:450] - :lsp/code-lens-resolve 68ms
2026-03-17T12:20:15.689Z INFO [clojure-lsp.handlers:450] - :lsp/code-lens-resolve 84ms
2026-03-17T12:20:15.940Z INFO [clojure-lsp.feature.diagnostics.built-in:324] - :internal/built-in-linters.unused-public-vars 1312ms
2026-03-17T12:20:15.960Z INFO [clojure-lsp.feature.diagnostics.built-in:315] - :internal/built-in-linters 1339ms
2026-03-17T12:20:16.020Z INFO [clojure-lsp.dep-graph:280] - :internal/maintain-dep-graph 59ms
2026-03-17T12:20:16.026Z INFO [clojure-lsp.feature.diagnostics.built-in:327] - :internal/built-in-linters.different-aliases 0ms
2026-03-17T12:20:16.027Z INFO [clojure-lsp.feature.diagnostics.built-in:330] - :internal/built-in-linters.cyclic-dependencies 0ms
2026-03-17T12:20:16.160Z INFO [clojure-lsp.feature.diagnostics.built-in:324] - :internal/built-in-linters.unused-public-vars 1339ms
2026-03-17T12:20:16.180Z INFO [clojure-lsp.feature.diagnostics.built-in:315] - :internal/built-in-linters 1365ms
2026-03-17T12:20:16.242Z INFO [clojure-lsp.dep-graph:280] - :internal/maintain-dep-graph 62ms
2026-03-17T12:20:16.250Z INFO [clojure-lsp.feature.diagnostics.built-in:327] - :internal/built-in-linters.different-aliases 0ms
2026-03-17T12:20:16.250Z INFO [clojure-lsp.feature.diagnostics.built-in:330] - :internal/built-in-linters.cyclic-dependencies 0ms
2026-03-17T12:20:16.307Z INFO [clojure-lsp.feature.diagnostics.built-in:321] - :internal/built-in-linters.find-linter-ignore-comments 281ms
2026-03-17T12:20:16.504Z INFO [clojure-lsp.feature.diagnostics.built-in:321] - :internal/built-in-linters.find-linter-ignore-comments 255ms
2026-03-17T12:20:17.498Z INFO [clojure-lsp.feature.diagnostics.built-in:324] - :internal/built-in-linters.unused-public-vars 1472ms
2026-03-17T12:20:17.521Z INFO [clojure-lsp.feature.diagnostics.built-in:315] - :internal/built-in-linters 1501ms
2026-03-17T12:20:17.521Z INFO [clojure-lsp.feature.file-management:157] - :reference-files/analyze 50827ms
2026-03-17T12:20:17.522Z INFO [clojure-lsp.server:133] - :lsp/publish-diagnostics 0ms
2026-03-17T12:20:17.522Z INFO [clojure-lsp.server:133] - :lsp/publish-diagnostics 0ms
2026-03-17T12:20:17.522Z INFO [clojure-lsp.server:133] - :lsp/publish-diagnostics 0ms
2026-03-17T12:20:17.522Z INFO [clojure-lsp.server:133] - :lsp/publish-diagnostics 0ms
2026-03-17T12:20:17.523Z INFO [clojure-lsp.server:133] - :lsp/publish-diagnostics 0ms
2026-03-17T12:20:17.523Z INFO [clojure-lsp.server:133] - :lsp/publish-diagnostics 0ms
2026-03-17T12:20:17.523Z INFO [clojure-lsp.server:133] - :lsp/publish-diagnostics 0ms
2026-03-17T12:20:17.523Z INFO [clojure-lsp.server:133] - :lsp/publish-diagnostics 0ms
yeah the problem here is the analyze, which will in the end become a clj-kondo call which might be where this will become a issue :/ it's similar if you clean .lsp/.cache and restart lsp, I suppose this takes a while right?
yeah, with a clean cache => :lsp/initialize 66174ms
even so 47s for a utils functin reference vs 66 for whole project seems a lot
also, switching branches seems a lot more intensive compared to the initialization. At times all my cpu's are hitting 100%.
I guess the good news is that clojure-lsp is efficiently utilizing all my cores 😅
yes, that's is what I see we can improve, we limit the parallelism somehow
it should be slower but smoother for user
what I typically see is • I open file A and look at some clojure function. The code lens shows "2 references" (from some other buffers) • I navigate to one of the call sites in some other file (file B) • I remove the function call. Maybe I'll save file B or maybe not, doesn't matter. • I switch back to buffer A • buffer A still lists "2 references" in the code lens for the function • I make some trivial change to file A (e.g. add an empty line) => The code lens is updated and now correctly shows "1 references"
I tried to hack together some "refresh LSP diagnostics when the buffer in the window changes" using window-buffer-change-functions but didn't get very far. I started thinking maybe there's something wrong with my LPS setup 🤷
I think this is what watchers is for