Repository navigation
Description
codegraph index <path> accepts an explicit path, then discards it whenever that path has no .codegraph/codegraph.db of its own — walking up to the filesystem root and rebuilding the first initialized ancestor it finds. It prints a normal Done and never says which project it actually indexed.
Version 1.5.0, verified against tip d6d1728.
Repro (no git involved)
#!/usr/bin/env bash
set -eu
T="$(mktemp -d)/reproindex"; mkdir -p "$T/parent/child"; cd "$T"
printf 'def parent_only():\n return 1\n' > parent/p.py
printf 'def child_only():\n return 2\n' > parent/child/c.py
( cd parent && codegraph init . ) # initialize ONLY the parent
( cd parent/child && codegraph index . ) # ask for the CHILD, explicitly
ls -d parent/child/.codegraph 2>/dev/null || echo "child has no index"
( cd parent/child && codegraph status | grep -i '^Project' )Actual:
2. ask codegraph to index the CHILD, by explicit path
● 4 nodes, 2 edges in 363ms
└ Done
3. what actually happened
child/.codegraph created : NO <-- the request was silently ignored
'codegraph status' from the child reports:
Project: /tmp/tmp.hpPpmKR0dq/reproindex/parent
Expected: either index the directory that was named, or refuse and say why. Not a silent substitution reported as success.
Cause
resolveProjectPath in src/bin/codegraph.ts:
function resolveProjectPath(pathArg?: string): string {
const absolutePath = path.resolve(pathArg || process.cwd());
if (isInitialized(absolutePath)) return absolutePath;
// Walk up to find nearest parent with CodeGraph initialized
let current = absolutePath;
const root = path.parse(current).root;
while (current !== root) {
const parent = path.dirname(current);
if (parent === current) break;
current = parent;
if (isInitialized(current)) { /* ...use the ancestor... */ }
}The walk has no stopping condition short of the filesystem root — no repo boundary, no --force-style confirmation, no log line. findNearestCodeGraphRoot in src/directory.ts has the same shape.
Fall-back-to-ancestor is a reasonable default for a query run from a subdirectory (that is what findNearestCodeGraphRoot is for). It is a poor default for index, whose entire job is to build an index at a location the caller named.
Why it matters in practice
The severity comes from how far the walk can travel. My repos live under a shared parent:
~/personal_projects/
├── testgraph/{.bare,main,...} <- 40+ sibling repos
├── runecho/...
└── ...
Running codegraph index . from an un-indexed repo there walked up past the repo and built a 279 MB index of ~11,000 files across 55 unrelated projects at ~/personal_projects/.codegraph. Once that exists, every subsequent query from any un-indexed directory beneath it resolves to that index and answers from the wrong codebase — still silently. On one occasion the run was interrupted, leaving 880,880 unresolved references, so answers were not merely from the wrong project but truncated as well.
git worktree layouts are especially exposed: sibling worktrees are never initialized themselves, so the walk escapes the repo on the first index in a fresh worktree. But as the repro shows, git is not required — any un-indexed subdirectory does it.
The existing guard (Refusing to index … it looks like your home directory, #845) only catches the case where the walk reaches $HOME or a filesystem root. It does not fire for an intermediate ancestor, which is the common case.
Existing related work, which does not cover this
src/sync/worktree.ts already documents this walk as worktree-unaware, and PR #312 warns when a query borrows another worktree's index — but that is scoped to worktrees nested inside a checkout, applies to MCP queries, and does not apply to index.
Suggested fix
For index specifically, one of:
- Index the path that was named. If it has no index, treat it as
init(or tell the user to runinit). - Keep the fallback but make it loud —
using the index at <ancestor> (…/child is not initialized)— so a wrong answer is at least visible. - Bound the walk at a repo boundary (
.gitpresent, worktree or not).
(1) matches what the argument means. (2) is the smallest change that removes the silence.
Note on discovery
Found while pointing the tool at a project of my own; the workaround is to use codegraph init <path> for a first index and never codegraph index <path>. Happy to send a PR for whichever option you prefer.
Activity
docs: correct the codegraph mechanism — it was inferred, not checked (#…
wstczyw commentedon Aug 11, 2026
I reproduced this against current
mainatc6aaa20358cd6adcd04b87bdef8e5803ad146f3a, using a genericindexed parent directory containing an independent, unindexed nested Git
repository.
One additional concern from source inspection:
resolveProjectPath()is alsoused by
sync,uninit,unlock,status, and the read/query commands.Therefore this is broader than
index: an explicit child path can select anancestor for both read and mutation operations. In particular, mutation
commands should not silently act on an ancestor selected for an explicit path.
A fail-closed workaround for automation is:
git -C <target> rev-parse --show-toplevelcodegraph status <git-root> --json.projectPath === git-root.unlock, or uninitialize the ancestor.
For an upstream fix, one possible command policy is:
init <path>: exact path, as today.index,sync,uninit,unlock): require an exact initialized project or fail while reporting thedetected ancestor.
VCS root.
status --json: exposerequestedPath,projectPath, and a resolution valuesuch as
exact,ancestor, oruninitialized.Useful regression cases would cover:
index;
This avoids requiring Git for all projects while making explicit paths and
mutation commands fail closed.
inth3shadows commentedon Sep 4, 2026
Status: still reproduces on 1.6.0 (npm latest), unchanged from the report. The
## Suggested fixabove still applies as written.Two files, when
childholds exactly one. Afterwards:parent/child/.codegraph— does not exist; the explicitly named path never got an index.parent/.codegraphnow holds both:parent_fnp.pychild_fnchild/src/c.pySo the ancestor index was silently rebuilt and widened to absorb the child, the named path was ignored, and the command reported success. "Indexed 2 files" is the only hint, and only if you already know the target's file count.
Verified on 1.6.0, isolated fixture outside any real project tree.
colbymchenry commentedon Sep 8, 2026
Fix landed (Linux verified)
PR: #1797
Commit:
327ea5aad5cfbba8a3cddeffffd1e7a88650bb3eUpstream used: #1689 by @danusha2345 (cherry-picked; author preserved)
Root cause
codegraph index <path>calledresolveProjectPath, which walks upward to the nearest initialized ancestor when<path>has no index — so an explicit child path silently rebuilt the parent and reportedDone.Fix
An explicit path names the project to rebuild and is not a hint to look elsewhere. Uninitialized explicit paths exit 1, name the ancestor that would have been rebuilt, and point at
codegraph initfor the named path. Barecodegraph index(cwd) still resolves upward.Linux verify (fail → pass)
On tip before the fix (
374b3b42): init parent only →index .from child →Done; child has no.codegraph;statusfrom child reports the parent.After the fix: same repro exits 1 with:
CodeGraph not initialized in …/childandThe nearest initialized project is …/parent …; child still has no.codegraph; parent DB mtime unchanged.Focused suite:
npx vitest run __tests__/cli-index-explicit-path.test.ts— 2 passed.Closing this issue as completed; #1689 will be closed as superseded by #1797.
fix(cli): index <path> never rebuilds an ancestor instead (#1524) (#1797