Repository navigation
Description
What problem does this solve?
Agent-driven development workflows often use many short-lived Git worktrees for feature branches, PR review, verification, and parallel tasks. The current worktree/branch-root support from #401 makes the graph shape more explicit, but it still leaves a larger design question open: how should Codebase Memory MCP avoid treating mostly identical worktrees as fully independent indexes?
The remaining pain points are:
- duplicated indexing and storage for worktrees that mostly match the base branch
- project-list clutter from short-lived worktree paths
- query ambiguity when an MCP client is running inside a worktree but the canonical checkout is also indexed
- stale project entries after temporary worktrees are removed, related to Feature Request: Automatic deletion of project when underlying folder is deleted #286
- missing or stale branch-specific context if only the canonical checkout is considered
- unnecessary churn if every transient agent edit triggers a full or near-full reindex
Related prior discussion and implementation:
- Git worktree support #48 discussed broad Git worktree support and arbitrary worktree locations.
- fix(discover): skip .worktrees directory during indexing #37 skipped
.worktrees/to avoid nested duplicate indexing. - Support efficient indexing for Git worktree-based agent development workflows #351 proposed efficient indexing for worktree-heavy agent workflows.
- feat(git): add worktree branch roots #401 added first-class branch/worktree graph roots and metadata.
- index_repository ignores .git/info/exclude (only .gitignore honored) → indexes excluded worktrees → OOM #489 / fix(discover): honor .git/info/exclude during file discovery #493 fixed one concrete nested-worktree OOM class via
.git/info/excludehandling. - index_repository silently ignores git global core.excludesFile — globally-excluded files are indexed #499 / fix: honor git global core.excludesFile as an ignore source #542 cover the remaining global Git exclude layer.
Proposed solution
I would like to discuss a committed-source, file-granularity overlay model on top of the existing branch/worktree awareness:
- Keep one canonical/base index for the repository's base branch.
- For each branch/worktree context, compute the merge base against the configured base branch.
- Index only files changed between
merge-base..HEADinto an overlay context. - At query time, resolve the active Git context and merge results as:
- overlay nodes/edges first
- overlay tombstones for deleted files or symbols
- base index for unchanged files
- If a file exists in the overlay, hide that file's base graph for that context.
- Treat worktree path as an attachment to a branch/HEAD context, not necessarily as the graph identity.
In other words, a repository would look conceptually like:
Project: example-repo
Base context: main @ abc123
full graph for committed main
Overlay context: feature/foo @ def456, base abc123
graph for changed committed files only
deleted-file tombstones
metadata: branch, worktree path, base SHA, head SHA
For a first implementation, I suggest indexing committed changes only:
overlay = git diff --name-status $(git merge-base HEAD <base-branch>)..HEAD
Dirty working-tree overlays could be considered later, but agents modify files quickly and frequently, so indexing uncommitted edits by default may add noise and churn.
Alternatives considered
Keep one full index per worktree
This is simple and mostly matches the existing external project model, but it duplicates unchanged source, grows storage with every short-lived agent worktree, and keeps project selection ambiguous.
Branch roots only
#401 is a useful foundation, but branch roots alone do not fully solve deduplication or overlay query semantics. They make the relationship visible; they do not yet make unchanged base files shared.
Shared content storage with separate project views
This could reduce storage while preserving the current project model, but it may still leave project-list clutter and context ambiguity unless grouped views and context-aware query resolution are added.
On-demand diff indexing only
This is attractive for temporary review worktrees, but the first query could be slower and long-lived branches probably still need persistent overlay metadata.
Questions to discuss
-
Should the overlay identity be branch-based, worktree-path-based, HEAD-SHA-based, or a combination?
-
Should v1 index only committed changes, or should it also support dirty working-tree changes behind an explicit option?
-
What should the default base branch be: detected default branch, configured project base, or caller-provided
base_branch? -
Should file-level replacement semantics be the first milestone? For example: if
src/foo.cchanged in the overlay, all base graph nodes fromsrc/foo.care hidden for that context. -
How should cross-file edges be resolved when unchanged base files refer to changed overlay symbols with the same qualified name?
-
Should existing MCP tools infer context from the caller's current working directory, or should tools accept an explicit context/project selector?
-
How should
search_graph,get_code_snippet, andtrace_pathexpose whether a result came from the base index or an overlay? -
How should stale overlays be garbage-collected after a worktree is deleted or a branch is rebased/force-pushed?
-
Should deleted files be represented as file-level tombstones only, or are node-level tombstones needed for precise symbol hiding?
-
Is this best developed as a sequence of small PRs, e.g. context metadata first, changed-file overlay indexing second, context-aware search third, and trace-path edge rewriting later?
Confirmations
- I searched existing issues and this is not a duplicate.
Activity
DeusData commentedon Jun 23, 2026
Really well-framed — thank you for pulling the worktree history (#48 / #37 / #351 / #401 / #489 / #493 / #499 / #542) together in one place.
The merge-base overlay model (one canonical base index + a changed-files-only overlay with tombstones, resolved against the active Git context at query time) is the right shape and avoids the N-near-identical-indexes blowup. Committed-changes-only for a first cut is the correct scope call; dirty-tree overlays can come later.
Tracking this under the architecture task #398, and the stale-entry-after-removal piece overlaps #286. Labeling
enhancement.ctaylor4874 commentedon Jun 24, 2026
Adding an operational data point from rolling this out org-wide (a large monorepo,
auto_index=false), since it reinforces the "committed-changes-only first cut" scope and touches a couple of the open questions.The base-reuse gap bites even before overlays exist. As of
main/ v0.8.1, project identity is still the slug ofgetcwd()—detect_session()→cbm_project_name_from_path()insrc/mcp/mcp.c— and thecanonical_rootresolved in #401 (src/git/git_context.c) isn't consulted for identity. Withauto_index=false, a session started inside a linked worktree therefore:maybe_auto_indexlogsautoindex.skip reason=disabledand bootstraps nothing,so the worktree gets no reuse of the canonical base graph at all — queries return "not indexed" — even though the base index is sitting in the cache and
canonical_rootalready points at it. Withauto_index=truethe failure mode flips to the duplication/churn you list (a full from-scratch index per worktree, never GC'd).Interim workaround, in case it's useful to others: I run a SessionStart hook that detects a linked worktree (
--git-dir != --git-common-dir) and injects the canonical project name (slug ofcanonical_root, computed to matchcbm_project_name_from_path) so the agent passesproject=<main>explicitly to the graph tools. It works, but it's a client-side band-aid and only ever exposes the base/committed graph — which is genuinely fine for most agent navigation (definitions, callers, impact); the branch delta is the part that needs the overlay.That experience suggests a useful first increment ahead of the full overlay (re Q10): context-aware read resolution to the canonical base when running inside a worktree — when
is_worktreeand no overlay exists, resolve reads against thecanonical_rootbase index instead of agetcwd-named phantom, and skip auto-indexing worktrees so the shared base is never mutated. That removes both the "not indexed" cliff and the duplicate-index blowup, and it's strictly a subset of the overlay model (overlay = empty set). From this rollout, my bias on the open questions:project/context selector as the override — the hook above is really just a manual version of the cwd-inference path.joshweir commentedon Jul 27, 2026
Answering Q6 from the query side, against
main@d587deab. Confirms @ctaylor4874 and adds one caseoverlays cannot reach.
Nothing is inferred today. Every graph read tool declares
"required":["project"](
src/mcp/mcp.c:434through640), and the only resolution isget_project_arg()(1385): fourspellings of the same key, then
resolve_project_tail()(1331), a unique-suffix rescue overcache-dir filenames.
detect_session()(10463) does slug onegetcwd(), but no query handler readssession_project- it reaches the watcher, auto-index, the daemon session surface and therelative-
repo_pathjoin at7521, and stops.Nor does anything error. A worktree holds the same files at the same relative paths, so a
main-checkout answer returns real symbols at plausible lines - just the base ref. A reviewer agent
asking who calls the function it changed gets the pre-change call graph and reports no regressions.
resolve_project_tail()widens it: when only the worktree is indexed, passing the repo name finds asingle tail match and adopts it silently.
The resolver for this already exists, one layer away.
ha_resolve_indexed_project_with_root()(src/cli/hook_augment.c:843) maps cwd to project: walk up,derive a name per level, probe
index_status, first hit wins; on a full missha_registry_project_for_path()(785) takes the project whoseroot_pathcontains the cwd, longestprefix winning - which also covers
--name <custom>, where no derived slug matches. It isstaticinsrc/cli/, unreachable from the tool layer. Lifting it is most of @ctaylor4874's increment. Fournotes:
get_architecturealready usespathfor a directory prefix that scopes analysis (535), so aresolution hint needs another key.
cbm_mcp_server_set_session_context()(mcp.h:149) is public and already fed a canonical root overthe wire by the daemon (
application.c:2434).resolve_store_internal()(1972) keeps one store open and closes it whenever the project changes,so probing
index_statusper level would reopen the queried DB on every call. Testing for<name>.dbon disk, asresolve_project_tail()does at1338, avoids that.allowed_root/CBM_ALLOWED_ROOTcheckrepo_pathgets(
7599).The case overlays cannot reach is subagents. A Claude Code subagent has its own working directory but
shares the parent session's MCP server process, whose
getcwd()still returns the parent'sdirectory - and
detect_session()caches that behindsession_detected, never re-reading. Nosession-level inference sees it, and neither does the roots protocol: a subagent's worktree is not a
session root.
The path must arrive per call.
So what I would send: an optional
cwdon the read tools, resolved through the lifted helper onlywhen
projectis absent, explicitprojectalways winning,projectmoving out ofrequiredinto ananyOfover the two - the shapecheck_index_coveragealready ships (612). No storage change, nonew graph semantics, no behaviour change for existing callers. Once overlays land, the same argument
can select the overlay context.
In scope for #573, or its own issue? Resolution is narrower than the overlay model and useful before
overlays land. Happy to open a focused issue and send that PR if you prefer the split.
Separately, and settling nothing here: when a query resolves a project whose
root_pathdoes notcontain the caller's path, warn in the response, and likewise when the index predates HEAD
(
cbm_git_context_talready carrieshead_sha). Own issue if you want it.lishixiang0705 commentedon Jul 31, 2026
Field report on the scale question, since @DeusData asked for worktree-heavy RC reports.
Scale: 379 live worktrees off a single 2,432-file repo.
Breakdown: 367 persistent (feature branches, PR audits, per-issue workspaces under
.worktrees/,orca/workspaces/,.claude/worktrees/), 12 ephemeral under agentscratchpads, 76 in detached HEAD. This is one repo, one developer, agent-per-worktree.
Measured cost of the current one-index-per-worktree model (v0.9.1-rc.1 + 2 commits,
plus an unrelated local patch to
detect_changesseed scoping; macOS arm64, measured onan isolated clone so as not to disturb the working setup):
git worktree addof identical content, then indexThe worktree shares zero bytes with the base index despite being byte-identical at
HEAD. Projected across the 379 worktrees: ~126 GB of near-duplicate index for one
repository.
Both
auto_indexsettings are lossy here, in different ways — this confirms@ctaylor4874's #573 comment from the query side, which I hit independently before
finding this thread:
auto_index=false(the default): a session started in a worktree derives a projectname that was never indexed,
maybe_auto_indexlogsautoindex.skip reason=disabled,and nothing bootstraps. The base graph is sitting in cache and
canonical_rootalready resolves to it, but identity is still the
getcwd()slug(
detect_session()→cbm_project_name_from_path()), so there is no reuse.auto_index=true: correct answers per worktree, but a full from-scratch index perworktree, and nothing GCs them when the worktree is removed. The watcher's stale-root
pruning only covers projects that were registered via a session; a worktree indexed
once and never reopened leaks its DB indefinitely.
I switched to
auto_index=truespecifically because @joshweir's point above is theworse failure: identical relative paths mean a base-ref answer looks entirely plausible,
so a review agent asking "who calls what I just changed" gets a pre-change call graph
and reports no regressions. Paying 340 MB per worktree is preferable to a silently
stale answer — but at 379 worktrees that tradeoff stops scaling.
Nothing to add to the design; the merge-base overlay shape in the issue body addresses
exactly this. Just adding the number, since "many short-lived worktrees" in the original
framing turns out to be three orders of magnitude of index for one repo.
TerrifiedBug commentedon Aug 18, 2026
Real-world confirmation of the "query ambiguity" bullet, with the cost measured
This is the bullet in your list that bites hardest in practice — "query ambiguity when an MCP client is running inside a worktree but the canonical checkout is also indexed" — and I have a measurement for it from an autonomous fleet on
v0.9.0(Linux x64).Setup: a dispatcher gives each coding-agent attempt its own git worktree of the repository, and separately maintains a canonical index of the repo's default branch (
4,935nodes,17,660edges, HEAD equal tomain, refreshed every 20 minutes on a timer). So a correct, fresh index for the code the agent is editing exists the entire time.Measured across the twelve most recent agent sessions:
Zero. And one capped session spent 586 tool calls — 216 edits against 264 read/grep discovery calls — before hitting its ceiling without ever querying the graph. Roughly half of every session's budget went to finding code by grep, which is precisely the work this server exists to make unnecessary.
The mechanism is the one you describe: the agent's cwd is a worktree whose absolute path was never indexed, so a cwd-derived project resolves to nothing, and the agent falls back to grep rather than discovering that a canonical index exists under a different key. Nothing is broken — the tools behave as documented — but the default path for a worktree-based client is silent emptiness.
A cheap intermediate step, well short of the full overlay model: when a client's cwd is a linked worktree, resolve the repository's canonical root (
git rev-parse --path-format=absolute --git-common-dir, then its parent) and, if an index exists for that root, answer from it — ideally with a flag in the response saying the result reflects the base branch and not the worktree's own edits. That alone converts every one of my zero-graph sessions into a useful one. The overlay model is clearly the better end state, and the tombstone design reads right, but the base-index mapping is what unblocks worktree clients today, and it needs no new indexing work.For anyone hitting this before either lands, the workaround is to pass the canonical project key explicitly rather than letting it derive from cwd. Verified here:
search_graphwith the canonical project returned the target function with exact file and line range in one call, andtrace_pathfor callers of another symbol correctly returned none — both against a worktree cwd. It works well; it just has to be known, which is the part a worktree client cannot discover on its own.feat(config): opt-in worktree_shared_index — one index per repository…
DeusData commentedon Oct 1, 2026
Thank you for bringing the worktree history and overlay design together, and to everyone who added concrete measurements, query-selection examples, and smaller intermediate proposals. I appreciate your patience with the long wait, and the earlier maintainer encouragement deserves acknowledgment.
Shared base indexes, committed-file overlays and tombstones, automatic canonical-base reuse, and per-call context selection remain design work. The operational examples are valuable input to that discussion. This closure does not establish that those capabilities have shipped or that a related PR implements the full proposal.
We’re keeping Issues focused on actionable defects, so I’m closing this feature request administratively and inviting continued design discussion in GitHub Discussions. The proposal, measurements, and suggestions remain available here; there is no need to resubmit them. Adoption and implementation scope remain undecided.
Thank you again for the care and detail in this discussion.