Repository navigation
Non-git silent on staleness #1948
Description
Version
codebase-memory-mcp 0.10.8
Platform
Windows (x64)
Install channel
GitHub release archive / install.sh / install.ps1
Binary variant
standard
What happened, and what did you expect?
I understand non-git repos aren't currently watched, just throwing this in there to be helpful:
A project indexed from a directory that is not a git repository is never refreshed again. It stays frozen at its first index for the life of the database, and nothing in the API indicates this.
and poll_project() has:
/* Skip non-git projects */
if (!s->is_git) {
return;
}The problem is not that these projects go stale. It is that going stale is invisible, and a stale graph produces confident wrong answers rather than obviously missing ones. A search_graph for a function that exists on disk returns:
total: 0
hint: "No nodes match this pattern. Check spelling or try a broader regex."
That is indistinguishable from a true negative, so a caller - especially an LLM agent - concludes the symbol does not exist and acts on it. Meanwhile index_status reports "status": "ready" with no last-indexed timestamp and no watched/unwatched flag.
Expected: either non-git roots get some refresh path (an mtime scan fallback), or - much cheaper - the API exposes the coverage, so a caller can decide whether to trust the answer and run an index or whatnot as needed.
Suggested fix:
- Surface it. Add a
watched: true|falsefield and a last-indexed timestamp toindex_status. Callers can then tell "no results" from "no results, and this project has not been checked since Tuesday." This is small and fixes the dangerous part — the silence. - Fallback scan. For non-git roots, poll a stat-walk signature (
path:size:mtimefolded into a hash) on the same adaptive interval already used for git roots.git_dirty_signature()inwatcher.calready does essentially this for dirty paths; the non-git case needs the same idea applied to the whole tree.
Additional Context:
Found while building an out-of-process freshness gate to work around this. Happy to test a patch — I have reproductions for both the git and non-git paths.
Reproduction
-
Create a plain directory that is not a git repository:
mkdir /tmp/nogit && cd /tmp/nogit cat > app.py <<'EOF' def alpha(): return 1 def beta(): return alpha() + 1 EOF
(No
git init- that is the whole point.) -
Index it:
codebase-memory-mcp cli index_repository --repo-path /tmp/nogit
-
Confirm the graph is populated:
codebase-memory-mcp cli search_graph --project <name> --name-pattern '.*alpha.*' # -> 1 result
-
Append a function to the file:
printf '\ndef gamma_uniquename():\n return beta() * 2\n' >> app.py
-
Wait as long as you like — minutes, hours, a server restart — then:
codebase-memory-mcp cli search_graph --project <name> --name-pattern '.*gamma.*'
Actual: total: 0, with the "check spelling" hint. Permanently — no poll interval ever covers this project.
Expected: the new function is found, or the response indicates the index may be stale.
Doing the same thing in a git init-ed directory behaves correctly: the
watcher notices and reindexes.
Logs
Diagnostics trajectory (memory / performance / leak issues)
Project scale (if relevant)
No response
Confirmations
- I searched existing issues and this is not a duplicate.
- My reproduction uses shareable code (a dummy snippet or a public OSS repository), not proprietary code.
Activity
DeusData commentedon Sep 25, 2026
Thank you, @chaking22, for such a careful report. The repro, the pointer at
poll_project(), and especially your point that silent staleness is the dangerous part made this easy to act on. We're doing both of your suggestions:index_statusthrough thefreshnessobject being built in feat(mcp): report truthful index freshness from checkout evidence #1561, so callers can tell "no results" from "no results, and this index may be old". We're keeping one freshness surface rather than adding a second key.codebase-memory-mcp config set watch_non_git true, thencodebase-memory-mcp daemon stopso the next session starts a daemon that reads it. Non-git roots are then polled on the same adaptive interval with a stat-walk signature (the indexer's own ignore rules, hashing path, size and mtime), very close to what you proposed. It's opt-in because it walks the whole tree on every poll. Your reproduction picks up the new function within one poll interval.We'd gladly take you up on your offer to test the patch, on Windows especially.
chaking22 commentedon Sep 27, 2026
Happy to be of some help - I took you up on the offer and ran some tests via windows sandbox. Found 1 issue with non-ASCII project roots (fairly serious, see below), passed everything else.
Env:
Actions:
Tests and Results:
watcher.baseline strategy=none, no reindex, searchtotal: 0(old behaviour, as documented)alpha→alphb, byte count unchanged)# 3: non-ASCII root is treated as missing
Indexing the folder works and watcher.watch logs the correct path, however polling shows:
No watcher.baseline or watcher.changed and the appended symbol is never found. Believe that poll_project() calls root_status() before anything else and root_status() uses stat(root_path, &st) on the UTF-8 path. With MinGW that goes through the ANSI code page, fails with ENOENT, and returns 'ROOT_MISSING'.
To add to that, it looks like root_missing feeds root pruning which deletes the project. Presumably this affects git and non-git projects. Tested this by setting grace period to 0 and it was removed after 3 polls ~26s.
feat(watcher): opt-in polling of non-git project roots (#1948)
Merge pull request #2333 from DeusData/fix/issue-1948
fix(watcher): report a tree-polled non-git root as strategy "tree"