LSP plugin extension conflict resolves silently — losing plugin appears enabled but server never spawns
Summary
When two LSP plugins register lspServers for the same extensionToLanguage mapping
(e.g., both claim .cs), the harness silently picks one and discards the other. There is
no warning at any surface — no log message, no Doctor diagnostic, no /plugin list
indication, no on-disk artifact. The losing plugin appears installed and enabled but
its server is never spawned.
This is a related-but-distinct concern from #32912 / #27692 (closed for inactivity,
asking for multi-server-per-language support). Even if multi-server support is out of
scope, the silent first-wins behaviour is a UX bug independent of that: users
cannot tell whether their plugin is broken, their tool installation is broken, or
something else is winning the race. The diagnostic gap costs hours.
The closing comment on #32912 explicitly invites a fresh issue if still relevant —
filing this as a focused, narrower bug.
Reproduction (real-world, observed 2026-06-01)
- Enable both plugins for
.cs:
csharp-lsp@claude-plugins-official(built-in, uses deprecatedcsharp-ls)roslyn-lsp@jodre11-plugins, a re-host of
unsafePtr/ClaudeCodeRoslynLspProxy
(uses Microsoft Roslyn LSP)
- Open a
.csfile in any multi-project solution. - Invoke the LSP
findReferencestool.
Expected (one of):
- Both servers spawn (multi-server, per #32912), OR
- A warning surfaces clearly indicating the conflict and which server won, OR
- The user-facing surfaces (
/plugin list, status line,/doctor) flag the inactive plugin.
Actual:
pgrep -af csharp-ls→ running.pgrep -af ClaudeCodeRoslynLspProxy→ not running.findReferencesreturns file-scoped results (Roslyn would have returned solution-wide).- No log message indicating the conflict — checked Claude Code's runtime logs and the
per-plugin LSP working dirs.
/plugin listshows both plugins as enabled.- The user has no way to know the wrong server is serving requests.
The broken state persists across restarts until the user manually flipscsharp-lsp@claude-plugins-official: false in ~/.claude/settings.json. Discovering
that requires reading the source of the plugin manifest format and noticing theextensionToLanguage overlap — not a reasonable expectation for end users.
Why this matters separately from multi-server support
The C# case is illustrative: shipping a built-in csharp-lsp (using deprecatedcsharp-ls) means every C# user installing a more capable third-party plugin
silently has the worse server win. The same pattern applies to anyone using a
better TypeScript LSP than the official one, a third-party Python server, etc.
Even if Anthropic's strategic answer is "one server per extension is the design,"
the silent part is the bug. A line like[lsp] WARN: extension '.cs' is registered by 2 enabled plugins (csharp-lsp@claude-plugins-official, roslyn-lsp@jodre11-plugins). Using csharp-lsp; disable the other to switch.
on first plugin resolution would have saved the entire investigation.
Concrete asks (any one of these would close the gap)
- Log a warning when extension registration is contested, naming both plugins
and which one was selected.
- Surface the conflict in
/plugin listand/doctoras a soft warning beside
the losing plugin.
- Document the resolver order so users at least know what to expect (currently
ambiguous whether it's marketplace order, install order, alphabetical, or
something else).
Environment
- Claude Code: 2.1.158 (Bedrock)
- macOS Darwin 25.5.0
- Two enabled
.csLSP plugins as described above
Related
- #32912 (closed 2026-05-30 for inactivity) — multi-server-per-language feature
- #27692 (closed earlier, also inactivity) — original duplicate
- #53399 —
.lsp.jsonlocation quirk for third-party plugins - #33552 —
.lsp.jsonsettingsfield not propagated
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗