Background
Running index this project on C:\codes\zhimalab (a full codebase-memory-mcp index) only returned a generic failure, with the real cause completely hidden. It took a long time to pin down, and the root cause turned out to be an 85-byte stray file named nul that had never been committed to git. This post isn’t about how to index — it’s about why a generic error message can send you on such a long detour.
Symptom: an error you can’t locate from
{"project":"C-codes-zhimalab","status":"error",
"hint":"Pipeline failed. Check repo_path exists and contains source files."}
“Path doesn’t exist / no source files”? The repo is right there, with 1,265 files under src/. That hint is a catch-all — every pipeline error looks like this, and the real error is swallowed. I tried CBM_LOG_LEVEL=trace, CBM_INDEX_LOG, and CBM_LSP_DISABLED — none of them surfaced the detail (the worker exited cleanly with code 0, and the logs only ever showed the same generic error).
Investigation: two wrong turns
Wrong turn #1: suspecting the broken cache tree
The session’s cbm daemon was running against a corrupted cache tree at C:\c\Users\peini\.cache\codebase-memory-mcp (leftover from a botched --dir install), and the main zhimalab.db was missing from the formal cache. My first instinct was to blame it. But olympic indexed fine in the same cache → ruled out.
Wrong turn #2: C:\tmp’s DACL interference
To reproduce, I created a minimal TS repo and a minimal Python repo under C:\tmp — both failed, leading me to the wrong conclusion that “new projects always fail; only already-indexed ones succeed via incremental”. Only when I put test repos under C:\codes and both succeeded on the first try did I realize: C:\tmp has an insecure DACL, so test repos placed there get rejected by the cache-private check — entirely unrelated to the problem I was chasing.
The real isolation: clean clone + bisection
The key step was git clone to a clean path (under C:\codes, avoiding C:\tmp) and indexing that:
- Committed content (clean clone) → success (7,817 nodes) → the problem isn’t in what’s committed;
- Added the working-tree differences back one by one: the modified
.cbmignore→ success; the staged rename → success; - Added the four stray root files (
nul,{G.difficulty,{G.topic,tmpclaude-c878-cwd) back → failure; - Bisected → only
nultriggers the failure; the other three are harmless.
Root cause: a Windows reserved device name
nul is a Windows reserved device name (like con, prn, aux). When the indexer does open("C:/codes/zhimalab/nul"), Windows treats it as the null device rather than a normal file, the pipeline read fails, and the whole index fails.
The nasty part: .cbmignore already had a nul exclusion rule, and it couldn’t help — file enumeration/opening happens before ignore filtering, so by the time the rule could apply, the pipeline had already crashed. And the error was conveniently wrapped as “the repo has no source files”, which made it baffling.
The file’s content was a stray ls error captured by a misdirected redirect:
ls: cannot access 'C:codeszhimalabsrccomponentstutorials': No such file or directory
Pure accidental junk, never committed to git.
Fix
rm -f nul # delete the stray file
Re-ran the full index: 27 seconds, 13,624 nodes / 45,794 edges, status: ready.
Retrospective: how to nail this fast next time
- Don’t take the generic hint literally. “repo_path exists and contains source files” is a catch-all; the tool swallows the real error.
- First thing, scan for reserved device names:
ls -lathe repo root fornul/con/prn/aux/com1-type files, and delete any found — this is the #1 known pitfall for indexing on Windows. - Don’t put test repos under
C:\tmp: its insecure DACL reliably fabricates a “new projects fail” illusion. UseC:\codesor your home directory. - The fastest isolation is
git cloneto a clean path: in one step it separates “committed content” from “working-tree state” — stop guessing at logs. - Suspect untracked stray files first: they’re not in git so they’re the easiest to overlook, and that’s exactly where this culprit was hiding.
CBM_LOG_LEVEL/CBM_INDEX_LOG/CBM_LSP_DISABLEDwon’t rescue you from this class of error — don’t waste time on them.
Finally, a tip: turn “scan for stray files before indexing” into a one-command script (scan + delete + run the index) so index this project becomes a single command. This repo now has cbm-index.sh, which scans and deletes reserved-device-name strays, then runs the index — with built-in retry for conflicts with the daemon’s background auto-index. One implementation detail worth noting: this kind of per-file scan must use pure bash builtins (${f##*/}, case) — per-file $(basename)/grep hangs after a few hundred iterations on Git Bash due to subprocess accumulation.