Skip to content

lsp: match server answers to graph nodes across path-separator spellings - #603

Merged
zzet merged 1 commit into
zzet:mainfrom
pbednarcik:fix/windows-lsp-answer-join
Aug 18, 2026
Merged

lsp: match server answers to graph nodes across path-separator spellings#603
zzet merged 1 commit into
zzet:mainfrom
pbednarcik:fix/windows-lsp-answer-join

Conversation

@pbednarcik

Copy link
Copy Markdown
Contributor

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 the
way back into the graph
. With zero staged or confirmed edges, usefulYield stayed 0 and
the productivity checkpoint concluded the server was unproductive:

LSP enrich: pass cut by productivity checkpoint — request volume flowed with near-zero
useful yield   window=120 windows_elapsed=1 requests=150 useful_yield=0

150 correct textDocument/implementation answers, all discarded, pass cancelled 145s into
a 5400s deadline.

To rule out the server I spoke raw LSP to csharp-ls against the same ~90-project solution:
workspace/symbol non-empty at T+30.5s, correct cross-solution implementation answer at
T+31.8s. The server was never the problem.

Root cause

Graph FilePaths carry both separator spellings across store vintages — older Windows
rows use \ after the repo prefix (repo/pkg\File.cs) while newer rows and every
URI-derived path use / (URIToRepoRel ends in filepath.ToSlash). The answer path
joined the two with exact string matches in three places:

  1. lspGraphView.nodesByFile — keyed by verbatim n.FilePath, looked up with
    URI-derived slash paths (matchNodeByFileLine, matchCallableByFileLine,
    findDeclarationNode). Every lookup missed.
  2. confirmRefMatchesSiterefPath == siteRel string equality between a URI-derived
    path and an edge's stored path. Every confirm failed.
  3. The reference-hop batch feeding GetFileNodesByPaths — an exact file_path IN (...)
    query given slash spellings that backslash rows can never match.

Same bug class as the file_dir generated-column issue (#593), one layer up: any exact
join between a URI-derived path and a stored path silently drops everything on Windows.

Fix

  • viewPathKey slash-normalizes nodesByFile keys and every lookup entry point.
  • confirmRefMatchesSite compares slash-normalized paths.
  • storePathSpellings fetches both spellings in the reference-hop batch, since a
    mixed-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:

  • LSP-origin edges: 0 → 33,841, all at confidence 1.0 — calls 26,178,
    implements 3,426, overrides 2,944, extends 1,187, plus instantiates / references /
    annotated / typed_as / returns.
  • Pass result: confirmed 5,474 existing heuristic edges, added 26,782 new ones,
    24,620 nodes enriched.
  • The pass ran 84.5 minutes to its deadline instead of being cancelled at 2m25s; hover
    phase: 24,620 ok, 0 errors, 0 reconnects across 1,224 document opens.
  • Battery on my counted fixture repo: byte-identical across runs, no regressions.

The fix is language-agnostic — the pyright/TS/gopls targeted phases hit the same joins,
which matches the confirmed: 0 their 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.

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
zzet merged commit 96408d9 into zzet:main Aug 18, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants