Skip to content

stdio MCP projectPath silently queries an ancestor index from a nested Git repository #2110

Closed

Description

@wstczyw

Summary

When codegraph_explore is called over stdio with projectPath inside an independent nested Git repository, CodeGraph walks past that Git boundary to the nearest ancestor .codegraph index. If the parent index excludes the nested repository, the call succeeds with symbols from the parent project and gives no ownership or "not indexed" warning.

Environment

  • @colbymchenry/codegraph 1.6.0 (Windows, stdio MCP)
  • Reproduced on main at f4ddf50 (1.6.1)

Minimal reproduction

T="$(mktemp -d)"
mkdir -p "$T/parent/nested"
printf 'nested/\n' > "$T/parent/.gitignore"
printf "export function parentOnly() { return 'parent'; }\n" > "$T/parent/parent-only.ts"
printf "export function nestedOnly() { return 'nested'; }\n" > "$T/parent/nested/nested-only.ts"

git -C "$T/parent" init
git -C "$T/parent" add .gitignore parent-only.ts
git -C "$T/parent" commit -m repro-parent

git -C "$T/parent/nested" init
git -C "$T/parent/nested" add nested-only.ts
git -C "$T/parent/nested" commit -m repro-nested

# Initialize only the parent repository.
codegraph init --yes "$T/parent"

Start codegraph serve --mcp with the current directory set to $T/parent, complete the MCP initialize handshake, then send this JSON-RPC tool call:

{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "codegraph_explore",
    "arguments": {
      "query": "parentOnly nestedOnly",
      "projectPath": "$T/parent/nested",
      "maxFiles": 10
    }
  }
}

Actual result

The response is success-shaped and contains parent-only.ts / parentOnly. It does not contain nested-only.ts, and it does not say that the requested nested Git repository is unindexed.

For comparison, the same call against a standalone repository with no .codegraph at or above it returns the expected "isn't indexed with codegraph" guidance.

Expected result

When the ancestor index excludes an independent nested Git repository (here through the parent's .gitignore), an explicit projectPath inside that repository should not silently borrow the ancestor's index. If the nested repository has no .codegraph of its own, return the normal not-indexed guidance, or require an explicit opt-in before using the ancestor index. Nested repositories that the parent index does include, and ordinary subdirectories of the same Git worktree, can keep the current nearest-index behavior.

Root cause

ToolHandler.getCodeGraph(projectPath) calls findNearestCodeGraphRoot(projectPath), and findNearestCodeGraphRoot walks parent directories until the filesystem root without checking Git ownership. The extraction side correctly respects the parent's .gitignore, so the parent index can omit the nested repository while MCP still selects that parent index.

Suggested fix

Add a regression test with two independent Git roots, a parent .gitignore entry for the nested root, and an MCP stdio projectPath call. Then, when the resolved index does not include projectPath's Git repository, either stop resolution at that Git boundary or fail closed instead of answering from the ancestor. Preserve an explicit, documented opt-in if maintainers want ancestor indexes to serve such repositories anyway.

Activity

  1. wstczyw commented on Sep 30, 2026

    @wstczyw
    Author

    Related: #1524, the same ancestor-index lookup for codegraph index <path>, fixed only for index in #1797. On main, detectWorktreeIndexMismatch (src/sync/worktree.ts) returns null when the nested repo's git common dir differs, so no borrowed-index warning appears for this case either.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions