Skip to content

Nested git repos never reindex on branch switch: watcher only tracks session_project, index_status reports false freshness #1429

Description

@xnng

Summary

In a multi-repo monorepo workspace (parent git root + many nested independent git checkouts), background auto-watch / reindex only tracks the MCP session project (cwd root). Branch switches and HEAD moves in already-indexed nested repositories are ignored.

Worse: index_status / list_projects surface live branch / head_sha from disk git, while the knowledge graph remains on the previously indexed tree. Agents therefore treat a stale graph as fresh and answer structural questions incorrectly with no error signal.

This is a high-severity reliability issue for agent workflows: silent wrong answers, not a hard failure.

Environment

Item Value
Version codebase-memory-mcp 0.9.0 (darwin-arm64 release binary)
OS macOS (arm64)
Config auto_index=true, auto_watch=true, auto_index_limit=50000
Client Agent MCP session with cwd = monorepo parent
Cache default ~/.cache/codebase-memory-mcp

Setup shape (common agent monorepo)

/path/to/workspace/                 ← git root; MCP session_root
  group/nested-service/             ← nested independent git repo (already indexed)
  other-nested-repos/               ← also indexed as separate projects
  • Session project name: derived from parent path (e.g. Users-...-workspace)
  • Nested project name: e.g. nested-service (indexed earlier via index_repository)

The nested repo has mutually exclusive branch trees (different modules/files per branch). Example: branch A has module service-variant-a/, branch B has service-variant-b/ instead.

Reproduction

  1. Start MCP from the parent monorepo root. Confirm autoindex/watcher only registers the session project:
level=info msg=autoindex.start project=<session-project> path=/path/to/workspace
level=info msg=watcher.watch project=<session-project> path=/path/to/workspace
level=info msg=watcher.baseline project=<session-project> strategy=git files=yes

No watcher.watch line appears for the nested project.

  1. Ensure nested repo is indexed and on branch A:
codebase-memory-mcp cli index_status --project nested-service
# nodes ~N, graph contains symbols under service-variant-a/
  1. Clean switch nested repo to a very different branch B:
cd group/nested-service
git status --porcelain   # empty
git switch branch-b
# service-variant-a gone from disk; service-variant-b present
  1. Wait well beyond poll interval (minutes). Re-check:
codebase-memory-mcp cli index_status --project nested-service

Actual

Surface After branch switch
Live files on disk Branch B tree
index_status.git.branch / head_sha Branch B (live)
Graph nodes / edges Unchanged from branch A
search_graph for branch-A-only symbols Still hits (e.g. paths under service-variant-a/)
search_graph for branch-B paths Missing / empty
search_code (disk grep) Correctly reflects branch B
stderr watcher.reindex for nested project Never

Expected

Either:

  1. Nested indexed projects under (or beside) the session are watched and reindexed when their HEAD moves; or
  2. Query surfaces refuse / flag staleness when graph snapshot ≠ current HEAD; and
  3. index_status exposes an indexed HEAD (or stale: true), not only live git metadata that always looks current.

Control: manual reindex works

codebase-memory-mcp cli index_repository \
  --repo-path /path/to/workspace/group/nested-service \
  --name nested-service \
  --mode fast
  • Branch B → fewer/different nodes; service-variant-b/* present, variant A gone from file_hashes
  • Switch back + reindex → graph matches branch A again

So the pipeline can rebuild correctly; automatic path never targets the nested project.

Note: omitting --name derived a second project name from absolute path, leaving the original project stale. Separate naming footgun, but not the core bug.

Root cause (source read on main / v0.9.0-era tree)

1. Watcher registration is session-scoped only

register_watcher_if_enabled only watches srv->session_project / srv->session_root:

/* Register the session project with the background watcher for ongoing
 * change detection — unless auto_watch is disabled. */
static void register_watcher_if_enabled(cbm_mcp_server_t *srv) {
    if (!srv->watcher || srv->session_project[0] == '\0' || srv->session_root[0] == '\0') {
        return;
    }
    ...
    cbm_watcher_watch(srv->watcher, srv->session_project, srv->session_root);
}

Already-indexed nested projects are never registered unless the MCP session cwd is that nested root.

2. Watcher can detect checkout when registered

watcher.c tracks git rev-parse HEAD and treats HEAD movement (commit / checkout / pull) as a change. Nested repos never reach this path.

3. Parent git HEAD does not move when a nested repo switches branches

Even continuous reindex of the session/parent project does not rebuild the nested project's DB. Nested .git trees are separate indexes.

4. False freshness compounds the failure

index_status / project list git fields read live git state (also discussed in #1213). After a nested branch switch, agents see matching branch/sha and assume the graph is current — while search_graph still returns symbols deleted on disk.

Related open issues (distinct but compounding):

This report is specifically: unwatched nested projects + live git metadata on stale graphs.

Impact

  • Agent monorepos with many nested git checkouts are a common CBM install shape.
  • Silent structural wrong answers on branch-divergent code (different modules / layouts per branch).
  • Worse than “index missing”: tools return confident, high-quality looking wrong results.
  • No reliable client-side freshness check today without external bookkeeping.

Severity: high for agent reliability (silent incorrect graph).

Suggested fixes (shortest path first)

  1. Watch all indexed projects (or all projects whose root_path is under session root / configured roots), not only session_project.
  2. Persist indexed_head_sha (and branch) at successful index time; expose in index_status with stale = (indexed_head_sha != live_head_sha).
  3. On stale, either auto-queue reindex or return an explicit staleness flag on graph tools so agents do not trust results blindly.
  4. Keep Watcher re-index leaves MCP server serving a stale cached store (POSIX, silent) #1191/MCP session serves a stale graph indefinitely after the background watcher re-indexes #1296 store invalidation so successful reindex is visible to long-lived MCP sessions.
  5. Docs: state clearly that auto_watch only covers the session project today, and nested multi-repo setups need per-repo reindex or separate sessions until (1) lands.

Workaround (current)

After every git switch / checkout in a nested indexed repo:

codebase-memory-mcp cli index_repository \
  --repo-path <nested-repo> \
  --name <existing-project-name> \
  --mode fast

Or keep one clone per long-lived branch as separate project roots (avoids in-place branch switches).

Offer

Happy to refine the repro into a small fixture (parent repo + nested repo with two branches of divergent file trees) if useful for a regression test.

No activity

Activity on this issue will appear here.

Activity

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

    bugSomething isn't workingeditor/integrationEditor compatibility and CLI integrationparsing/qualityGraph extraction bugs, false positives, missing edgespriority/highNeeds near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker.ux/behaviorDisplay bugs, docs, adoption UX

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions