lsp: match server answers to graph nodes across path-separator spellings - #603
Merged
Merged
Conversation
Graph FilePaths carry both separator spellings across store vintages — older Windows rows use '\' after the repo prefix while newer rows and every URI-derived path use '/'. The enrichment answer path joined the two with exact string matches: the nodesByFile lookups, the reference confirm comparison, and the GetFileNodesByPaths batch all missed every row on a backslash-spelled store. A perfectly answering server then scored zero useful yield and the productivity checkpoint cancelled the pass — on Windows no LSP enrichment could ever land an edge. Key nodesByFile by the slash-normalized path and normalize each lookup, compare confirm paths slash-normalized, and fetch both spellings in the reference-hop batch.
zzet
approved these changes
Aug 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Symptom
On Windows, LSP enrichment could never land a single edge —
origin='lsp_resolved'stayed at 0 no matter what. The failure shape was misleading: the server spawned, loaded,
and answered flawlessly; the sweep sent its requests fine (outgoing paths go through
filepath.Join, which tolerates either separator); but every answer was dropped on theway back into the graph. With zero staged or confirmed edges,
usefulYieldstayed 0 andthe productivity checkpoint concluded the server was unproductive:
150 correct
textDocument/implementationanswers, all discarded, pass cancelled 145s intoa 5400s deadline.
To rule out the server I spoke raw LSP to csharp-ls against the same ~90-project solution:
workspace/symbolnon-empty at T+30.5s, correct cross-solutionimplementationanswer atT+31.8s. The server was never the problem.
Root cause
Graph
FilePaths carry both separator spellings across store vintages — older Windowsrows use
\after the repo prefix (repo/pkg\File.cs) while newer rows and everyURI-derived path use
/(URIToRepoRelends infilepath.ToSlash). The answer pathjoined the two with exact string matches in three places:
lspGraphView.nodesByFile— keyed by verbatimn.FilePath, looked up withURI-derived slash paths (
matchNodeByFileLine,matchCallableByFileLine,findDeclarationNode). Every lookup missed.confirmRefMatchesSite—refPath == siteRelstring equality between a URI-derivedpath and an edge's stored path. Every confirm failed.
GetFileNodesByPaths— an exactfile_path IN (...)query given slash spellings that backslash rows can never match.
Same bug class as the
file_dirgenerated-column issue (#593), one layer up: any exactjoin between a URI-derived path and a stored path silently drops everything on Windows.
Fix
viewPathKeyslash-normalizesnodesByFilekeys and every lookup entry point.confirmRefMatchesSitecompares slash-normalized paths.storePathSpellingsfetches both spellings in the reference-hop batch, since amixed-vintage store can hold either.
Results (same production store, same drill, before → after)
My production C# monorepo (~117k nodes), eager enrichment with the csharp-ls
solution-targeting branch also applied:
implements 3,426, overrides 2,944, extends 1,187, plus instantiates / references /
annotated / typed_as / returns.
24,620 nodes enriched.
phase: 24,620 ok, 0 errors, 0 reconnects across 1,224 document opens.
The fix is language-agnostic — the pyright/TS/gopls targeted phases hit the same joins,
which matches the
confirmed: 0their passes always reported on Windows.Tests
windows_answer_join_test.go: view lookups across both spelling directions(backslash-spelled node / slash lookup and the reverse), reference-confirm across
spellings, and the both-spellings helper. Full
go test ./...at my Windows baseline.