Skip to content

bug: update-graph.sh hook rebuilds the wrong repo with a stale global codegraph binary #2134

Description

@carlos-alm

Found during dogfooding v3.16.0

Severity: Medium
Command: PostToolUse hook .claude/hooks/update-graph.sh (fires on Edit/Write)

Reproduction

  1. 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).
  2. 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.
  3. 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/.
  4. 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:

  1. 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.
  2. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingdogfoodFound during dogfooding

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions