Environment
- Version:
codebase-memory-mcp 0.10.3 (--version), single Mach-O arm64 binary at ~/.local/bin.
- OS: macOS (Darwin 25.6.0), daemon under launchd (
daemon start, KeepAlive=false).
- Config (
config list): auto_watch = true, auto_index = false, auto_index_limit = 50000, ui-lang = auto.
Summary
Two coupled behaviors make auto_watch=true fail to keep an index fresh in exactly the setups where
background freshness matters most — headless/CI clients, subagent-only workflows, and any project indexed
under a --name override:
- The watcher is session-scoped. The background git watcher is registered only when an interactive
MCP client connects (watcher.watch) and torn down on disconnect (watcher.unwatch). A print-mode /
one-shot client acquires no watch, ephemeral subagents share a parent bridge and hold none, and a project
with no live interactive session goes stale indefinitely — despite auto_watch=true.
- The watch keys by the path-derived name, never the
--name override. An index stored under an
override name receives zero watcher.watch events (orphaned/stale). Worse: when a real session later
opens the same path, the daemon creates a second full index under the derived name instead of
refreshing the override-named one — a silent duplicate (2× disk, and the original stays stale).
The server-advertised contract "once indexed, watched projects auto-refresh in the background" silently does
not hold for these cases.
Reproduction (minimal)
# 1. Index a repo under an override name.
codebase-memory-mcp cli index_repository --repo-path /path/to/repo --name my-alias
# 2. Edit + commit a tracked file in /path/to/repo.
# 3. Inspect — 'my-alias' is never refreshed by the watcher:
codebase-memory-mcp cli index_status --project my-alias # stale; mtime unchanged
# 4. Open an INTERACTIVE MCP session with cwd=/path/to/repo (auto_watch=true), idle.
# 5. Daemon log now shows a watch on the DERIVED name + a brand-new index:
# watcher.watch project=<path-derived-name> path=/path/to/repo
# -> project '<path-derived-name>' is created & indexed; 'my-alias' STILL stale.
<path-derived-name> = the path with separators flattened, e.g.
/Users/me/code/project-a → Users-me-code-project-a.
Evidence (live 0.10.3 daemon)
A. Every watch binds the derived name; override names are never watched. grep -c over the daemon log
(project names anonymized; counts verbatim):
watcher.watch project=… |
occurrences |
alpha (override name) |
0 |
beta (override name) |
0 |
Users-me-code-project-a (derived, same repo as alpha) |
6 |
Users-me-code-project-b (derived) |
31 |
Sample line — the project key is always the flattened path, the --name never appears:
level=info msg=watcher.watch project=Users-me-code-project-a path=/Users/me/code/project-a
The override-named project (alpha) sat stale for ~2 days until a manual index_repository.
B. The duplicate index, on disk. Two coverage reports for the SAME repo, ~1 min apart, identical
parse_partial=11 file set — one under the derived name, one under the override name:
logs/Users-me-code-project-a-<t1>.log # 02:39:29 project=Users-me-code-project-a
logs/alpha-<t2>.log # 02:40:42 project=alpha
C. Session lifecycle is the trigger. A steadily-used project (project-b above) shows 31 watcher.watch
interleaved with watcher.unwatch — connect/disconnect cycling — confirming the watch lives and dies with
the interactive session, not the project.
D. config --help documents the design as session-scoped:
auto_watch default=true Register background git watcher on session connect
Impact
- Headless / CI / print-mode (
-p) clients: never fresh.
- Subagent-only workflows: never fresh (shared parent bridge holds the only watch, if any).
- Any
--name-overridden project: never fresh and silently duplicated when a session opens its path.
- Users read "watched projects auto-refresh" and trust a stale index. The duplicate wastes disk and hides
the staleness behind a fresh-looking twin under a different name.
Requested changes
-
Persistent watch_projects config (or a watch add/list/remove subcommand). A declared set of repo
roots the daemon watches unconditionally at startup, independent of any client session. Decouples
freshness from an interactive session and fixes the headless/subagent/CI case. Today config exposes only
4 keys — none declare a standing watch.
-
Root-path-matched watch binding (the core fix). Resolve the watch target — and the index it
refreshes — by canonical repo root path, not by display name. If a project already exists for that
root under any name, refresh THAT project; never create a derived-name twin for an already-indexed root.
Keep --name as a pure display alias over the path identity. This single change eliminates both the
orphaning (#A) and the duplicate (#B).
-
A rename / alias tool. rename_project --project <old> --name <new> (or an alias) so an existing
index can be renamed to the derived name / carry an alias without today's index-new → verify → delete-old
migration dance.
-
Document + retune the hook-augment deadline. The SessionStart/SubagentStart hook-augment enforces a
hard 2000 ms default deadline; on cold subagent starts it silently times out — 16
hook-augment: deadline_exceeded ms=2000 events in a single session — starving subagents of graph
context (frequently misread as "subagents don't use CBM"). The log line itself points to
CBM_HOOK_DEADLINE_MS, but the knob is undocumented. Please (a) document the env var, (b) raise the
default (≈4000 ms), and/or (c) make hook-augment non-blocking so a slow augment degrades to "no extra
context" without affecting the turn.
Happy to test a patch against this install and report back with the same log-grep evidence.
Environment
codebase-memory-mcp 0.10.3(--version), single Mach-O arm64 binary at~/.local/bin.daemon start,KeepAlive=false).config list):auto_watch = true,auto_index = false,auto_index_limit = 50000,ui-lang = auto.Summary
Two coupled behaviors make
auto_watch=truefail to keep an index fresh in exactly the setups wherebackground freshness matters most — headless/CI clients, subagent-only workflows, and any project indexed
under a
--nameoverride:MCP client connects (
watcher.watch) and torn down on disconnect (watcher.unwatch). A print-mode /one-shot client acquires no watch, ephemeral subagents share a parent bridge and hold none, and a project
with no live interactive session goes stale indefinitely — despite
auto_watch=true.--nameoverride. An index stored under anoverride name receives zero
watcher.watchevents (orphaned/stale). Worse: when a real session lateropens the same path, the daemon creates a second full index under the derived name instead of
refreshing the override-named one — a silent duplicate (2× disk, and the original stays stale).
The server-advertised contract "once indexed, watched projects auto-refresh in the background" silently does
not hold for these cases.
Reproduction (minimal)
<path-derived-name>= the path with separators flattened, e.g./Users/me/code/project-a→Users-me-code-project-a.Evidence (live 0.10.3 daemon)
A. Every watch binds the derived name; override names are never watched.
grep -cover the daemon log(project names anonymized; counts verbatim):
watcher.watch project=…alpha(override name)beta(override name)Users-me-code-project-a(derived, same repo asalpha)Users-me-code-project-b(derived)Sample line — the project key is always the flattened path, the
--namenever appears:The override-named project (
alpha) sat stale for ~2 days until a manualindex_repository.B. The duplicate index, on disk. Two coverage reports for the SAME repo, ~1 min apart, identical
parse_partial=11file set — one under the derived name, one under the override name:C. Session lifecycle is the trigger. A steadily-used project (
project-babove) shows 31watcher.watchinterleaved with
watcher.unwatch— connect/disconnect cycling — confirming the watch lives and dies withthe interactive session, not the project.
D.
config --helpdocuments the design as session-scoped:Impact
-p) clients: never fresh.--name-overridden project: never fresh and silently duplicated when a session opens its path.the staleness behind a fresh-looking twin under a different name.
Requested changes
Persistent
watch_projectsconfig (or awatch add/list/removesubcommand). A declared set of reporoots the daemon watches unconditionally at startup, independent of any client session. Decouples
freshness from an interactive session and fixes the headless/subagent/CI case. Today
configexposes only4 keys — none declare a standing watch.
Root-path-matched watch binding (the core fix). Resolve the watch target — and the index it
refreshes — by canonical repo root path, not by display name. If a project already exists for that
root under any name, refresh THAT project; never create a derived-name twin for an already-indexed root.
Keep
--nameas a pure display alias over the path identity. This single change eliminates both theorphaning (#A) and the duplicate (#B).
A rename / alias tool.
rename_project --project <old> --name <new>(or analias) so an existingindex can be renamed to the derived name / carry an alias without today's index-new → verify → delete-old
migration dance.
Document + retune the hook-augment deadline. The SessionStart/SubagentStart hook-augment enforces a
hard 2000 ms default deadline; on cold subagent starts it silently times out — 16
hook-augment: deadline_exceeded ms=2000events in a single session — starving subagents of graphcontext (frequently misread as "subagents don't use CBM"). The log line itself points to
CBM_HOOK_DEADLINE_MS, but the knob is undocumented. Please (a) document the env var, (b) raise thedefault (≈4000 ms), and/or (c) make hook-augment non-blocking so a slow augment degrades to "no extra
context" without affecting the turn.
Happy to test a patch against this install and report back with the same log-grep evidence.