Skip to content

v0.171.0

Choose a tag to compare

@github-actions github-actions released this 17 Oct 11:48
· 29 commits to main since this release

Upgrading: nothing to do (INDEX_VERSION is unchanged). Two exit codes
change. While the MCP server builds the index, the CLI query commands exit 1
and ask you to retry; they answered from the part built so far with exit 0.
And health-check exits 1 after an index run that died, until
incremental-index or the MCP server's next start re-indexes. To pin the
binary back: npm i -g @sdsrs/code-graph@0.170.1 (or, from source, cargo install --locked --git https://github.com/sdsrss/code-graph-mcp --tag v0.170.1 --features embed-model; without the feature, search is FTS5-only), then
delete ~/.cache/code-graph/bin/code-graph-mcp, the auto-update copy, which
is used ahead of an older binary, and set CODE_GRAPH_NO_AUTO_UPDATE=1 in the
environment Claude Code runs in, so the plugin does not fetch it again. That
pins the binary only: the plugin's hooks, statusline and doctor come with the
plugin.

Fixes from an end-to-end pass that used the CLI, the MCP server and the plugin
the way a user does, on flask, hono, django, leveldb, cobra, gin, echo,
spring-petclinic, rack, sinatra and kubernetes' hack/lib.

Queries wait for the MCP server's build

While the MCP server builds the index, the CLI query commands (callgraph,
impact, refs, show, similar, …) exit 1 with The index is still being built (d/t files); answers from it would be incomplete. Retry in a few seconds. They used to answer from the part built so far with exit 0, and
their freshness writes could fail the server's build with database is locked. health-check still answers, and grep falls back to plain grep.

A build counts from the server's first status write, {"s":"starting"} to
.code-graph/indexing-status.json, made before it walks the tree, and the
first progress line comes before the first batch, not after it. The
statusline shows code-graph: ↻ indexing… for it, and SubagentStart says nothing while it
runs. Each write names the server's pid, and a status file counts as a build
only while that pid is found or some process holds .code-graph/index.lock,
which the server holds for its whole life: a killed server's leftover file
stops refusing queries at once, unless another process (a rebuild-index)
holds the lock, and then for up to 120 s. The lock matters in a sandbox with
its own PID namespace (Claude Code's Bash sandbox), where the server's pid is
never found: sandboxed queries answered during the server's build and failed
2 of 3 flask builds.

A health-check or query beside a build no longer fails it

Every open of the index, health-check and the queries included, rewrote the
schema version, which is a committed write. A build that had already read in
its transaction then failed at once with database is locked: on flask, 2 of 3
incremental-index runs failed beside a health-check loop, and the
statusline runs health-check on every render. The version is now written
only when it changes; 5 of 5 runs passed beside about 90 health-checks each.

A query beside another index run (a CLI build, the MCP server's run) no
longer refreshes the files it reads. Its writes contended with the run's: a
cold incremental-index on django beside a show loop could fail with
database is locked. The query now answers from the index as it is and
reports the files stale, as it did on contention.

An index run that died is reported

An index run that was killed, or failed, left an index short of what it was
writing, and health-check said OK (a startup build that died at 506 of
3,453 files read OK: 3294 nodes … 506 files). It now says UNHEALTHY: the last index run did not finish (killed or failed), so files and edges may be missing. Run: code-graph-mcp incremental-index (it re-indexes in full) and
exits 1; --format json gives "reason": "interrupted". The CLI queries
print the same warning on stderr and still answer. doctor's index repair
(incremental-index) clears it, and so does the MCP server's startup run;
SessionStart starts nothing for it, since a CLI run beside the server's raced
its writes. A run's marker now stays until its last graph phase has run; it
used to be removed before those phases.

Symbol names

  • Type::method (Rust, C++) selects the same definitions as Type.method, in
    the CLI and the MCP tools. It was Symbol not found, and callgraph DBImpl::Write answered for DBImpl::Writer. A :: spelling the index
    stores itself is kept: Ruby's class Minitest::Test, bash's
    kube::log::status.
  • A dotted name that matches no definition still falls back to its last part,
    and now says so on stderr: No definition is named 'PetRepository.findById'; answering for 'findById'.
  • A markdown heading beside a code definition of the same name no longer makes
    the name ambiguous (flask's README # Flask made callgraph Flask, impact Flask and refs Flask refuse), and get_ast_node, find_similar_code,
    find_references and similar take the code definition, not the heading.
  • In Java, C#, C/C++ and Dart a constructor no longer makes its class
    ambiguous: a function named like its type inside the type's body, or a
    C/C++ T::T defined in the type's file. refs, impact, callgraph and
    MCP get_call_graph answer for the class. A method named like its class in
    other languages (TS, Python, Ruby) and a method beside a same-named type
    (Go's type Negotiate struct and Context.Negotiate) are still two
    definitions, as before.
  • callgraph's text tree roots at a code definition, neither a test nor a
    markdown heading, and among those at one with callers: flask's callgraph Flask printed a test's subclass and (48 test callers hidden) with none of
    the real class's callers, and leveldb's callgraph Cache rooted at
    doc/index.md.
  • impact's count of what its confidence floor excluded leaves out edges past
    --depth (hono resolveHttpServer reported 39 that --min-confidence ambiguous did not add) and names what it counts: call edges, which can be
    more than the callers they add (flask impact post --depth 1: 215 edges;
    dropping the floor adds 1 caller and 27 tests).
  • affected finds C/C++ test files by their gtest TEST cases; on leveldb it
    found none (util/coding_test.cc and the rest).
  • Outside git, incremental-index names the command that indexes there,
    rebuild-index --confirm; it and the queries pointed at each other.

Plugin

  • SessionStart starts no incremental-index while the MCP server's build is
    in progress. Started beside it (git HEAD newer than the index, the server
    first), the CLI run raced the server's writes and failed its startup run:
    django, 3 of 3.
  • The edit hook passes on impact's caveats with its counts: the excluded call
    edges and a traversal that stopped early.
  • The prompt hook passes on an ambiguous name's definitions and the CLI's own
    advice (--file or --node-id); it said nothing for such names.
  • doctor's index repair builds without embeddings, which ran past its 120 s
    limit and reported a failure.
  • With CODE_GRAPH_NO_AUTO_UPDATE=1, a missing binary's message says it is
    being installed; it asked for a manual install the launcher was already
    doing.
  • The snapshot workflow template has snapshot create compress the snapshot
    and uploads its .blake3 checksum with it. It uploaded the snapshot alone,
    and installs refuse a snapshot without its checksum, so what it published
    could not be installed. Its header now gives the version that writes the
    checksum (0.36.0).
  • README: the statusline's uninstall handoff, auto-indexing (the per-edit hook
    is off by default; the server's watcher re-indexes edits at its next query
    tool call), CODE_GRAPH_QUIET_HOOKS, and a model-checksum command that
    works.

Not covered

  • A C++ constructor defined in another file than its class (leveldb's
    DBImpl::DBImpl in db_impl.cc), or out of line for a class template
    (SkipList<Key, Comparator>::SkipList), still makes the class ambiguous.
  • refs Rack::Protection beside a README # Rack::Protection answers for the
    heading, as in 0.170.1, and a Ruby root-namespace spelling the index does not
    store (show ::Minitest::Test) is Symbol not found.
  • A query killed while its freshness pass writes leaves the interrupted
    marker, and health-check reports the index unhealthy until a run
    re-indexes in full: incremental-index, or the MCP server's next index run
    or start.
  • SessionStart's incremental-index (started when git HEAD is newer than the
    index) can still run beside the MCP server's startup run when SessionStart
    starts first. On django, with the server started 50 ms after it, 3 of 3
    sessions ended with the interrupted marker (the release review): the CLI run
    fails with database is locked, health-check reports the index unhealthy
    and the statusline shows ✗, until a run re-indexes in full as above.
    0.170.1, which does not report an interrupted run, read OK in the same
    test.
  • A server killed before its first progress line, while its status says
    starting, leaves no marker. health-check reports the index empty; a
    query then indexes only the files it reads and answers from them, and
    health-check reads OK (flask: show url_for indexed 8 of 109 files and
    said Symbol not found), as in 0.170.1.
  • For the first milliseconds of a cold server start, before its status write
    (up to ~20 ms on django and flask), the queries and health-check fail as
    in 0.170.1: index database is corrupt: file is 0 bytes while index.db is
    empty, then database is locked.
  • Where the run lock cannot be taken (Windows, a lock file that cannot be
    opened), an index run that died is not detected.
  • similar falls back to the bare name without the note, and --json and the
    prompt hook do not carry it.
  • The statusline shows ✗ for an interrupted index without the reason, and
    with no MCP server running nothing clears it until incremental-index or
    doctor runs.
  • impact X counts the excluded edges into every definition named X; MCP
    get_ast_node counts them for the one it selected (flask post: 215 and
    12).
  • cargo audit reports RUSTSEC-2026-0367, -0368 and -0369 in zip 7.2.0,
    which candle-core 0.9.2 requires for the embedding model. No candle
    release takes the fixed zip 9 yet (0.11.0 requires ^8.6.0); candle reads
    and writes archives and never calls the extract() the advisories concern.
    0.170.1 ships the same zip.