Found during dogfooding v3.16.0
Severity: Medium
Command: PostToolUse hook .claude/hooks/update-graph.sh (fires on Edit/Write)
Reproduction
- Have a globally-installed
codegraph binary on $PATH that is an older version than the repo's current package.json version (e.g. /opt/homebrew/bin/codegraph at v3.15.0, while the checked-out worktree is v3.16.0).
- From a Claude Code session whose cwd is set to worktree A (e.g.
.claude/worktrees/dogfood-3.16.0), build a fresh graph for worktree A with the correct (e.g. locally-installed 3.16.0) binary.
- Write or edit any file with a tracked source extension (see
EXTENSIONS/LANGUAGE_REGISTRY) anywhere on disk — including a path entirely outside worktree A, e.g. a scratch .sh script under /private/tmp/.../scratchpad/.
- Inspect worktree A's
.codegraph/graph.db build_meta table before and after step 3.
sqlite3 .claude/worktrees/dogfood-3.16.0/.codegraph/graph.db "SELECT * FROM build_meta;"
# codegraph_version | 3.16.0, node_count | 21031, edge_count | 43195
# ... write an unrelated /tmp/scratch.sh file via the Write tool ...
sqlite3 .claude/worktrees/dogfood-3.16.0/.codegraph/graph.db "SELECT * FROM build_meta;"
# codegraph_version | 3.15.0, node_count | 20361, edge_count | 43221 <-- changed!
Subsequent commands against worktree A's DB then print:
[codegraph WARN] DB was built with codegraph v3.15.0, running v3.16.0. Consider: codegraph build --no-incremental
Expected behavior
Editing/writing a file that lives entirely outside a given worktree/repo should never trigger a rebuild of that worktree's graph. And when a rebuild does run, it should use the same codegraph build associated with the project being analyzed, not whatever codegraph happens to be first on $PATH.
Actual behavior
update-graph.sh has two compounding bugs:
PROJECT_DIR=$(git rev-parse --show-toplevel ...) resolves the session's current working directory's repo root, not the actual directory containing tool_input.file_path. So writing a tracked-extension file anywhere (even a completely unrelated path in /private/tmp, even a different git repo) rebuilds whatever repo the session happens to be cd'ed into.
command -v codegraph picks up the first codegraph on $PATH — typically a globally-installed/linked binary — rather than a binary matched to the project under test (e.g. the locally npm install-ed dev/release version). If that global binary is an older version, the hook silently reruns the build with older (potentially less accurate, per this release's ~100+ resolver/extractor fixes) parsing logic, downgrading build_meta.codegraph_version and producing different node/edge counts, with 2>/dev/null suppressing any evidence.
This is a real correctness hazard for any workflow (like /dogfood) that builds a graph for repo A in one worktree while other Claude Code activity (even unrelated scratch-file writes) can trigger a hook that silently overwrites repo A's graph with results from a stale binary.
Root cause
.claude/hooks/update-graph.sh:
- Derives
PROJECT_DIR from git rev-parse --show-toplevel (cwd-based) instead of validating that FILE_PATH is actually inside the target project.
- Resolves the build binary via
command -v codegraph (global $PATH) with no check that it matches the project's own installed version, and swallows build stderr (2>/dev/null), hiding the version-mismatch WARN from the user entirely.
Suggested fix
- Validate that
FILE_PATH is actually a descendant of PROJECT_DIR (or derive PROJECT_DIR from FILE_PATH's own git toplevel) before triggering any rebuild.
- Prefer the project-local
node dist/cli.js (or node_modules/.bin/codegraph) over a global codegraph on $PATH, falling back to the global binary only if no local build exists — and surface (not swallow) the build's stderr so a version mismatch is visible instead of silently rewriting build_meta.
Found during dogfooding v3.16.0
Severity: Medium
Command: PostToolUse hook
.claude/hooks/update-graph.sh(fires on Edit/Write)Reproduction
codegraphbinary on$PATHthat is an older version than the repo's currentpackage.jsonversion (e.g./opt/homebrew/bin/codegraphat v3.15.0, while the checked-out worktree is v3.16.0)..claude/worktrees/dogfood-3.16.0), build a fresh graph for worktree A with the correct (e.g. locally-installed 3.16.0) binary.EXTENSIONS/LANGUAGE_REGISTRY) anywhere on disk — including a path entirely outside worktree A, e.g. a scratch.shscript under/private/tmp/.../scratchpad/..codegraph/graph.dbbuild_metatable before and after step 3.Subsequent commands against worktree A's DB then print:
Expected behavior
Editing/writing a file that lives entirely outside a given worktree/repo should never trigger a rebuild of that worktree's graph. And when a rebuild does run, it should use the same codegraph build associated with the project being analyzed, not whatever
codegraphhappens to be first on$PATH.Actual behavior
update-graph.shhas two compounding bugs:PROJECT_DIR=$(git rev-parse --show-toplevel ...)resolves the session's current working directory's repo root, not the actual directory containingtool_input.file_path. So writing a tracked-extension file anywhere (even a completely unrelated path in/private/tmp, even a different git repo) rebuilds whatever repo the session happens to be cd'ed into.command -v codegraphpicks up the firstcodegraphon$PATH— typically a globally-installed/linked binary — rather than a binary matched to the project under test (e.g. the locallynpm install-ed dev/release version). If that global binary is an older version, the hook silently reruns the build with older (potentially less accurate, per this release's ~100+ resolver/extractor fixes) parsing logic, downgradingbuild_meta.codegraph_versionand producing different node/edge counts, with2>/dev/nullsuppressing any evidence.This is a real correctness hazard for any workflow (like
/dogfood) that builds a graph for repo A in one worktree while other Claude Code activity (even unrelated scratch-file writes) can trigger a hook that silently overwrites repo A's graph with results from a stale binary.Root cause
.claude/hooks/update-graph.sh:PROJECT_DIRfromgit rev-parse --show-toplevel(cwd-based) instead of validating thatFILE_PATHis actually inside the target project.command -v codegraph(global$PATH) with no check that it matches the project's own installed version, and swallows build stderr (2>/dev/null), hiding the version-mismatch WARN from the user entirely.Suggested fix
FILE_PATHis actually a descendant ofPROJECT_DIR(or derivePROJECT_DIRfromFILE_PATH's own git toplevel) before triggering any rebuild.node dist/cli.js(ornode_modules/.bin/codegraph) over a globalcodegraphon$PATH, falling back to the global binary only if no local build exists — and surface (not swallow) the build's stderr so a version mismatch is visible instead of silently rewritingbuild_meta.