Skip to content

auto_watch is session-scoped and keyed by path-derived name — override-named indexes never refresh, and a watching session silently forks a duplicate index #1660

Open

Description

@7hr1LL

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:

  1. 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.
  2. 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

  1. 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.

  2. 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).

  3. 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.

  4. 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.

Activity

  1. added
    bugSomething isn't working
    priority/highNeeds near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker.
    on Aug 18, 2026
  2. DeusData commented on Aug 18, 2026

    @DeusData
    Owner

    Thank you for the measured watcher lifecycle and duplicate-index evidence. For triage, this needs to remain an umbrella rather than become one implementation PR because it contains four independent surfaces:

    1. The correctness bug where an override-named index is orphaned and a path-derived duplicate is created
    2. Persistent daemon-owned watches independent of an interactive session
    3. A public rename or alias tool
    4. Hook deadline documentation and default behavior

    The root-identity correctness bug is the high-priority core. The other three require separate product and API decisions and should be tracked as focused follow-ups. Any PR against this umbrella should address only one of those boundaries. Thank you for collecting the evidence that connects them without hiding their different review costs.

  3. DeusData commented on Sep 26, 2026

    @DeusData
    Owner

    Thank you, @7hr1LL, for the measured watcher lifecycle! One part of the root-identity bug now has a fix up. #2344 changes an unnamed index_repository on a root that was first indexed under an explicit name: it now updates that project in place instead of forking a second index, and if more than one project owns the root, it fails loudly and names them.

    That doesn't yet cover the session path you described. Session auto-index and watcher registration still key on the path-derived name, so the orphaned override-named index is still open there. It stays the high-priority core of this umbrella. The persistent-watch, rename and hook-deadline items remain separate decisions, as noted before.

  4. njuptlzf commented on Sep 29, 2026

    @njuptlzf

    Small refinement from 0.11.0 / Windows, in case it narrows the fix:

    In my measurements it is not "an interactive MCP client connects" that registers the watch, but specifically the client that creates the session-managed daemon. A second, fully interactive client that joins the already-active daemon registered nothing for its cwd (watcher.watch / baseline / changed / unwatch = 0/0/0/0), and daemon stop is refused while any client holds it (daemon: NOT stopped — 1 committed client(s) still use it).

    Supporting detail from one log: daemon.start 580 vs daemon.lifetime_end 579 (reason=runtime_exited) — each watch lives only for its creating client's daemon lifetime. Same log, per-project watch counts: session workspace 598, a project repeatedly indexed by CLI 118, the nested repo actually being edited 0.

    Fuller write-up with numbers on #1429.

  5. DeusData commented on Oct 19, 2026

    @DeusData
    Owner

    Thank you for the Windows follow-up distinguishing the daemon-creating client from clients that join it. That narrows the remaining session/watch registration case beyond the earlier unnamed-index change in #2344. I have no verified fix for that case to report yet; the persistent-watch and alias proposals still need separate decisions.

  6. DeusData commented on Oct 25, 2026

    @DeusData
    Owner

    Thank you for the Windows clarification about clients joining an existing daemon. The session/watch binding case remains outstanding beyond the earlier unnamed-index change. Sorry for the delay; this is waiting on maintainer follow-up, and I’ll follow up soon.

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 integrationpriority/highNeeds near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker.stability/performanceServer crashes, OOM, hangs, high CPU/memoryux/behaviorDisplay bugs, docs, adoption UX

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions