Plan: scope the LSP workspace singleton per client so instances coexist - #714
Conversation
The newest-wins workspace singleton keys its owner record on the workspace root alone, so every mdsmith lsp on a workspace contends for one slot and the newest supersedes the rest. That tears down the VS Code server the moment a Claude Code plugin server (or a second Claude terminal) initializes on the same repo. Plan: make the singleton claim opt-in via a client-supplied `singletonScope` token and key the owner record on (root, scope). VS Code sends `vscode.env.sessionId` (stable across an extension-host reload), so the upgrade hand-off still reaps the orphan; other clients send no token and coexist freely. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mb6qCg7oxErCU2p5JFH3eP
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files
☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Two independent xhigh design-review passes converged on a load-bearing flaw: vscode.env.sessionId is documented as changing "each time the editor is started" and is injected per extension-host process, so a leaked orphan host and its respawn may hold different ids — which would make (root, sessionId) keying never reap the orphan, strictly worse than today's root-only key for the one case the singleton exists for. Revisions: - VS Code now sends a per-workspace UUID persisted in context.workspaceState (disk-backed, provably stable across a reload/update) instead of env.sessionId. - Fold scope into the single workspaceKey seam (empty scope => legacy root-only key) instead of a parallel singletonKey. - Namespace the wire field as initializationOptions.mdsmith.singletonScope. - One gate: EnableWorkspaceSingleton stays the process capability; scope is only the key input and claim gate. - Add Backward compatibility (empty scope reproduces the legacy key; stale owner records are harmless), sharpen Rollout skew, and note multi-root re-keying is out of scope (unchanged today). - Strengthen TDD tasks/criteria: key-derivation unit test, absent/null initializationOptions decode, empty-scope no-op driven red/green, no-token-never-claims, bun:test for the token. - Two-window-same-folder limitation documented (not a regression). - Fix status to not-started (the implementation has not begun). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mb6qCg7oxErCU2p5JFH3eP
|
🟢 Merge Queue — picked up This PR is in the queue and will be batched with other Next: No action needed — you'll get another comment when CI starts on the batch. View merge queue run. |
|
🔵 Merge Queue — CI running Merged into batch branch Next: No action needed — you'll be notified when CI completes. |
|
✅ Merge Queue — merged This PR landed on Next: Done — nothing more to do here. |
Summary
Adds a plan to let several
mdsmith lspprocesses serve one workspace at the same time — the VS Code extension's server, a Claude Code plugin server running in the same VS Code, and a second Claude in another terminal — while keeping the VS Code extension-upgrade hand-off (old server stops, new starts) intact.This PR is plan only. No engine, server, or extension code changes yet.
Problem
The newest-wins workspace singleton (
internal/lsp/singleton.go), enabled in production atcmd/mdsmith/lsp.go, keys its owner record on the workspace root alone:So every
mdsmith lspon a workspace contends for one slot, the newest wins, and the older ones sendmdsmith/supersededand exit. The VS Code client suppresses restart on that signal (decideClose), so the VS Code server is torn down — and does not come back — the moment a Claude plugin server (or a second Claude terminal) initializes on the same repo.The singleton must be preserved for the one case it was built for: a VS Code extension update/reload leaves a leaked extension host that holds the old server's stdin open (no EOF) and stays alive by PID (so the
processIdwatchdog can't reap it). Newest-wins is what stops that orphan racing the freshly spawned server.Proposed design
initializationOptions.singletonScopetoken; key the owner record on(root, scope). No token → no claim, no supersede.vscode.env.sessionId— stable across an extension-host reload, unique per app session — so the orphan and the respawn share one slot and the upgrade hand-off still works.processIdcan't be used (orphan and respawn differ) andclientInfo.namecan't either (two Claude terminals shareclaude-code). A client-supplied, reload-stable token is the only identity that both reaps the orphan and lets independent clients coexist.Scope / non-goals
mdsmith lspkeeps its own in-processSessioncaches.processIdwatchdog both stay.Plan
plan/2606292015_lsp-multi-instance-coexistence.md— status 🔳, with TDD tasks and acceptance criteria covering the Go server, the VS Code extension, and the docs (docs/reference/cli/lsp.mdgains a multi-instance section; the VS Code guide's "Two mdsmith servers" note is updated).Checks
mdsmith check .passes (520 files, 0 failures).🤖 Generated with Claude Code
Generated by Claude Code