Skip to content

fix: v0.45.1 — refuse multi-repo workspace parent (cross-project index bleed)#45

Merged
Shahinyanm merged 1 commit into
masterfrom
fix/multi-repo-parent-guard
Jun 11, 2026
Merged

fix: v0.45.1 — refuse multi-repo workspace parent (cross-project index bleed)#45
Shahinyanm merged 1 commit into
masterfrom
fix/multi-repo-parent-guard

Conversation

@Shahinyanm

Copy link
Copy Markdown
Member

Problem

When a Claude Code session is launched from a non-git workspace parent that nests several project repos — e.g. /home/.../loom holding token-pilot, loom-host, aimux, task-jorunal — token-pilot indexed all of them into one ast-index. Symbol lookups then bled across projects: find_usages / read_symbol returned matches from the wrong repo, or symbol not found.

Root cause

start.sh always passes an explicit root:

PROJECT_ROOT="${CLAUDE_PROJECT_DIR:-$USER_CWD}"
exec node "$SCRIPT_DIR/dist/index.js" "$PROJECT_ROOT"

So in startServer explicitRoot is always truthy → the entire git-root narrowing block (if (!explicitRoot)) is skipped and the raw arg is used verbatim. When that arg is a workspace parent (not a git repo), isDangerousRoot doesn't catch it (it only guards system/home dirs), so ast-index scans every sibling repo.

Fix

New guard isMultiRepoParent(root) in core/validation.ts: returns true for a non-git directory with >=2 immediate child git repos. On match:

  • skipAstIndex is set (ast-index disabled) — fail safe instead of bleeding.
  • A warning tells the user to set CLAUDE_PROJECT_DIR to the specific project.

Wired into:

  • startServer (src/index.ts) — skipAstIndex + warning.
  • server.ts MCP-roots auto-detect (Strategy 1) — a client-reported parent is rejected too. Strategy 2 (git toplevel) is inherently single-repo, left untouched.

Unaffected: single-repo roots, monorepos, and roots that are themselves a git repo (submodules/vendored repos underneath stay intentional).

Tests

tests/core/multi-repo-parent.test.ts — 7 cases: >=2 child repos -> true; one/zero child repos -> false; root is itself a repo -> false; .git-as-file (submodule/worktree) counted; missing path / empty string -> false. tsc --noEmit clean.

Note: 3 pre-existing context-mode-detector test failures and full-suite parallel flakiness exist on master independently of this change (sequential run is clean aside from those 3).

🤖 Generated with Claude Code

…x bleed)

start.sh always passes an explicit project root, so startServer's git-root
narrowing (the `!explicitRoot` branch) never runs. When the session starts
from a non-git workspace parent nesting several project repos (e.g.
/work/loom holding token-pilot, loom-host, aimux), the raw parent was used
verbatim and ast-index indexed every sibling into one index — find_usages /
read_symbol then bled matches across unrelated projects. isDangerousRoot only
caught system/home dirs, so the parent slipped through.

Add isMultiRepoParent(root) guard (core/validation.ts): a non-git dir with
>=2 immediate child git repos. On match, ast-index is disabled (skipAstIndex)
and a warning tells the user to set CLAUDE_PROJECT_DIR to the specific
project — fail safe instead of bleeding. Wired into startServer and the
server.ts MCP-roots auto-detect. Single-repo roots, monorepos, and roots
that are themselves a git repo are unaffected.

Tests: tests/core/multi-repo-parent.test.ts (7 cases). tsc clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Shahinyanm Shahinyanm merged commit 44f89ea into master Jun 11, 2026
2 checks passed
@Shahinyanm Shahinyanm deleted the fix/multi-repo-parent-guard branch June 11, 2026 17:02
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.

1 participant