LSP diagnostics leak across sibling git worktrees

Resolved 💬 1 comment Opened Apr 18, 2026 by salehoun Closed May 27, 2026

Summary

When two Claude Code sessions share a repository root via sibling git worktrees, an Edit tool call in one session causes the other session's LSP layer to emit <new-diagnostics> for the edited file — but rendered as if the file had no tsconfig context. Every global is reported as missing (Cannot find name 'Promise', Cannot find name 'Set', Cannot find name 'Record'), every path alias is unresolvable (Cannot find module '@/...'), and every built-in method is missing from its type (Property 'indexOf' does not exist on type 'string', Property 'push' does not exist on type 'string[]'). The project's tsc --noEmit passes cleanly; the errors are purely an LSP-surface artifact that streams into unrelated sessions.

Environment

  • Claude Code version: latest as of 2026-04-17
  • OS: Linux
  • Project: Next.js 16 + TypeScript (repro reproducible on any TS project using "paths": { "@/*": ["./src/*"] })

Steps to Reproduce

  1. In a repo with two git worktrees (main tree at $REPO and a sibling at $REPO/.worktrees/<branch>/), start two Claude Code sessions:
  • Session A: cd $REPO && claude (main tree)
  • Session B: cd $REPO/.worktrees/<branch> && claude (sibling worktree)
  1. In Session B, run any skill or direct prompt that triggers Edit or Write tool calls on .tsx / .ts files under src/.
  2. Observe Session A's tool-result stream: <new-diagnostics> blocks appear for files inside .worktrees/<branch>/src/... — files Session A never opened.

Expected Behavior

Diagnostics emitted by Session A's LSP surface should be scoped to Session A's CWD-rooted project (the main tree at $REPO), not to sibling worktrees that are independent TypeScript projects with their own tsconfig and node_modules.

Actual Behavior

Session A receives <new-diagnostics> blocks for files edited in Session B's worktree. Every diagnostic is symptomatic of TypeScript running without a lib AND without the project's path aliases. Representative samples:

activity-log.ts
  ✘ Cannot find name 'Promise'
  ✘ Cannot find name 'Set'
  ✘ Cannot find name 'Record'
  ✘ Property 'push' does not exist on type 'SQL<unknown>[]'

TaskDetailModal.tsx
  ✘ Cannot find module '@/types'
  ✘ Cannot find module '@/hooks'
  ✘ Cannot find name 'Promise'

Root Cause Analysis

Three pieces of evidence, taken together:

  1. The tsconfigs in main and worktree are byte-identical (cmp returns equal). Both declare "lib": ["dom", "dom.iterable", "esnext"] and "paths": { "@/*": ["./src/*"] }.
  2. The worktree has its own fully-installed node_modules with lib.es2015.d.ts, typescript/lib/tsc.js, etc.
  3. Running tsc --noEmit inside the worktree produces no errors; running npx tsc --noEmit --listFiles in the main tree returns zero files matching .worktrees/ — the main-tree tsc does not walk into sibling worktrees.

So: neither tsc instance reproduces the error. The errors come from Claude Code's LSP integration, which appears to:

  • Watch files across the entire CWD tree from the main session's root,
  • Open each file in an isolated per-file language context when a sibling process modifies it,
  • Fail to associate that per-file context with any tsconfig — producing the "no lib, no paths" symptom pattern.

The "no lib" signature is decisive: a tsconfig misconfiguration would lose @/* aliases or a specific lib entry, not every global at once. Only a file opened with no project context loses Promise, Set, Record, built-in methods on strings/arrays, and aliases simultaneously.

What I Tried

Two workarounds were investigated and ruled out based on the documented Claude Code surface:

  1. Settings-level LSP scope knob — the Claude Code settings reference (https://code.claude.com/docs/en/settings.md, ~225 documented keys) has zero LSP / diagnostics scope configuration keys. No lsp.*, diagnostics.*, ignorePatterns, workspaceFolders, or equivalent.
  2. PostToolUse hook that strips diagnostic entries — the hooks reference (https://code.claude.com/docs/en/hooks.md) documents updatedMCPToolOutput as MCP-only. PostToolUse hooks can block or add context, but cannot mutate built-in tool result payloads.

A third workaround (excluding .worktrees/ from the project's tsconfig.json exclude array) is being landed downstream as a belt-and-braces hint for any LSP implementation that consults tsconfig discovery — but since the observed behavior suggests files are opened without a discovered tsconfig, this does not reliably stop the leak.

Requested Fix

A policy in Claude Code's LSP integration to scope file watching and diagnostic emission to the session's CLAUDE_PROJECT_DIR (or the git-worktree boundary, equivalently), so sibling worktrees do not leak <new-diagnostics> into unrelated sessions. Either:

  • Ideal: LSP watcher treats CLAUDE_PROJECT_DIR as the project root and ignores any file whose absolute path is outside it.
  • Acceptable workaround: a settings.json key (e.g., lsp.ignorePaths: [".worktrees/**"] or lsp.projectRoot) that lets projects opt out of the watcher for specific subtrees.

Operational Impact

Running 3+ parallel Claude Code sessions from a single repo-root (a common pattern for concurrent feature work across worktrees) multiplies the cross-session noise. Every sibling Edit sends spurious diagnostics to every peer session, wasting tokens, polluting context, and creating false urgency. The bug does not cause incorrect output — the LSP is wrong, the project compiles fine — but the noise is significant enough that a downstream project is shipping the tsconfig workaround in production.

Related Internal Tracking

  • RailVio task: SDLC-1011 (private tracker; happy to share redacted details on request)
  • Downstream tsconfig workaround PR: will link once merged

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗