Repository navigation
Project walk-up resolves $HOME as a project because $HOME/.codegraph is also the app-state dir #2
Description
Summary
.codegraph/ means two different things, and the project resolver cannot tell them apart:
<repo>/.codegraph/— a project index$HOME/.codegraph/— codegraph's own app state (telemetry.json,telemetry-queue.jsonl,update-check.json,daemons/,daemon.sock,daemon.pid)
Because the project resolver walks up from $PWD looking for .codegraph/, any session whose cwd is under $HOME and which has no index of its own terminates the walk at $HOME/.codegraph and resolves $HOME as its project root. It then takes daemon mode (decision order step 4, serve --mcp), spawns the full 4-process chain, fails to open the non-existent index, and stays resident for the life of the session.
Symptom
daemon.log, repeating indefinitely across restarts:
[CodeGraph daemon] Listening on /home/ericm/.codegraph/daemon.sock (pid 701885, v1.6.0). Idle timeout 300000ms.
[CodeGraph MCP] Failed to open project at /home/ericm: no such table: unresolved_refs
[CodeGraph MCP] Failed to open project at /home/ericm: no such table: unresolved_refs
$HOME/.codegraph/codegraph.db is 4096 bytes — an empty SQLite file created once and never populated.
Why it cannot be worked around by deleting it
Moving $HOME/.codegraph aside works for about four minutes:
09:40 mv ~/.codegraph ~/.codegraph.disabled-20260917
09:43 ~/.codegraph/update-check.json recreated
09:44 ~/.codegraph/telemetry-queue.jsonl recreated
App state is recreated on every run, so the directory that poisons the walk-up is guaranteed to exist whenever codegraph is in use. It can only be ignored, never removed.
Impact — this is a memory bug, not just a log-noise bug
Every Claude Code session spawns its own codegraph process chain. Measured on one machine (v1.6.0, Node 22.23.2, WSL2):
mode procs RSS
daemon (.codegraph/ resolved) 4 284M
direct (no .codegraph/ reachable) 2 131M
A session under $HOME with no index of its own pays the 284M daemon-mode path — the most expensive one — for a project that cannot be opened. The correct behaviour would be direct mode at worst, and arguably "no project, no server".
At 20 concurrent sessions that is the difference between fitting and not fitting in a 24GB VM.
Reproduce
mkdir -p ~/scratch-no-index && cd ~/scratch-no-index # any dir under $HOME with no index
codegraph serve --mcp
# -> resolves $HOME as the project, daemon mode, "no such table: unresolved_refs"Contrast with a cwd outside $HOME (e.g. /tmp/... when /tmp/.codegraph does not exist), which correctly falls to direct mode and logs:
[CodeGraph MCP] No .codegraph/ at or above <dir>: no default project, live sync disabled.
That is the message every no-index session should produce.
Suggested fix
Ranked by how much they change:
- Skip
$HOME/.codegraphin the project walk-up. One condition, fixes the bug, no migration, no layout change. - Separate the two namespaces. App state belongs in
$XDG_STATE_HOME/$XDG_CACHE_HOME(~/.local/state/codegraph,~/.cache/codegraph);.codegraph/stays a project marker only. This is the real fix and also resolves upstream Feature request: XDG directory spec support colbymchenry/codegraph#924, which asks for XDG support on tidiness grounds and does not mention this failure mode. - Treat an empty index as absent. A
codegraph.dbof <= 8KB has no tables; resolving to it guarantees theunresolved_refsfailure. Walking past it (or failing to direct mode) is strictly better than starting a daemon for it. A.codegraph/with nocodegraph.dbat all is a freshly initialised project and should still be honoured.
(1) and (3) are independent and can ship together ahead of (2).
Workaround in use here
A wrapper in front of the MCP command that performs the walk itself, skips $HOME, rejects indexes whose db is <= 8KB, and otherwise execs the real binary — falling back to the real binary on any unexpected condition. Where no real index is found it serves a minimal MCP server advertising zero tools (~11M), so the client handshake still completes.
Measured on this machine: $HOME-rooted sessions went from 284M + a permanent error loop to 11M and clean stderr; repos with a real index are unaffected.
Environment
- codegraph 1.6.0 (fork build
v1.6.0-em.1) - Node 22.23.2
- Linux 6.6.87.2 (WSL2), Ubuntu 24.04
- 52 real project indexes under the tree;
$HOME/.codegraphwas the only empty one, and the only one causing this
Activity
inth3shadows commentedon Sep 17, 2026
Correction — the root cause in the report above is wrong
I filed this having read the symptom but not the resolver. Reading
src/directory.tschanges the diagnosis, and therefore the fix.What I claimed: the walk-up finds any
.codegraph/directory, so$HOME/.codegraph(app state) captures it.What is actually true:
findNearestCodeGraphRoot()(src/directory.ts:158-177) already guards viaisInitialized(), which requirescodegraph.dbto be present. App state alone —telemetry.json,update-check.json,daemons/— does not capture the walk.The real defect is that
isInitialized()(src/directory.ts:93-101) tests existence ofcodegraph.db, never that it has a schema:A
codegraph.dbthat exists with zero tables — a failed or abandoned init — is accepted as a real index and permanently captures resolution for every directory beneath it. On this machine:created 2026-07-15, never populated.
$HOMEis an ancestor of everything, so it captured every session that had no index of its own.Consequently, one more claim above is wrong: I wrote that the directory "can only be ignored, never removed" because app state is recreated. The app state does come back — but without a
codegraph.db, so the recreated directory does not capture the walk. Moving it aside genuinely did fix the root cause. That is luck rather than design: anything that puts a db back restores the bug.Identical at v1.6.0 and current upstream
main(142 commits past the tag).Revised fix, in priority order
isInitialized()should validate the db has a schema, not merely exist. This is the actual bug and it is not$HOME-specific — a failed init anywhere poisons every descendant.os.homedir()fromfindNearestCodeGraphRoot(). Defence in depth, and upstream already applies exactly this reasoning to the down-scan ineligibleForSubprojectScan()(src/directory.ts:262-269, citing upstream prompt-hook: monorepo sub-project down-scan escapes cwd when a stray workspace manifest defeats the looksLikeProjectRoot gate ($HOME injects an unrelated project) colbymchenry/codegraph#1454): "The user's home directory and the filesystem root are never eligible.".codegraph/into$XDG_STATE_HOME/$XDG_CACHE_HOME. Structural fix; also upstream Feature request: XDG directory spec support colbymchenry/codegraph#924.The memory numbers and repro in the original report stand — only the causal explanation was wrong.
Upstream
Posted the corrected analysis on colbymchenry#924: colbymchenry#924 (comment)
Workaround in place here
codegraph-gateguards on both: it skips$HOMEand rejects any index whosecodegraph.dbis <=8KB, while allowing a.codegraph/with no db yet (a freshly initialised project). Both guards turn out to be the right ones — (2) and (1) respectively.inth3shadows commentedon Sep 26, 2026
Fixed on this fork's
main(22a3f5e) by carrying upstream PR colbymchenry#1913 (@danusha2345, merged here asbf37fca, upstream issue colbymchenry#1895). Acodegraph.dbwith no schema no longer counts as an initialized project, so an empty file in an ancestor such as$HOME/.codegraphcan't capture resolution any more.Reopen if a session under
$HOMEstill resolves$HOMEas its project on the fork'smain.