Repository navigation
v0.171.0
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 asType.method, in
the CLI and the MCP tools. It wasSymbol not found, andcallgraph DBImpl::Writeanswered forDBImpl::Writer. A::spelling the index
stores itself is kept: Ruby'sclass 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# Flaskmadecallgraph Flask,impact Flaskandrefs Flaskrefuse), andget_ast_node,find_similar_code,
find_referencesandsimilartake 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::Tdefined in the type's file.refs,impact,callgraphand
MCPget_call_graphanswer for the class. A method named like its class in
other languages (TS, Python, Ruby) and a method beside a same-named type
(Go'stype Negotiate structandContext.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'scallgraph Flaskprinted a test's subclass and(48 test callers hidden)with none of
the real class's callers, and leveldb'scallgraph Cacherooted at
doc/index.md.impact's count of what its confidence floor excluded leaves out edges past
--depth(honoresolveHttpServerreported 39 that--min-confidence ambiguousdid not add) and names what it counts: call edges, which can be
more than the callers they add (flaskimpact post --depth 1: 215 edges;
dropping the floor adds 1 caller and 27 tests).affectedfinds C/C++ test files by their gtestTESTcases; on leveldb it
found none (util/coding_test.ccand the rest).- Outside git,
incremental-indexnames the command that indexes there,
rebuild-index --confirm; it and the queries pointed at each other.
Plugin
- SessionStart starts no
incremental-indexwhile 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 (--fileor--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 createcompress the snapshot
and uploads its.blake3checksum 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::DBImplindb_impl.cc), or out of line for a class template
(SkipList<Key, Comparator>::SkipList), still makes the class ambiguous. refs Rack::Protectionbeside a README# Rack::Protectionanswers for the
heading, as in 0.170.1, and a Ruby root-namespace spelling the index does not
store (show ::Minitest::Test) isSymbol not found.- A query killed while its freshness pass writes leaves the interrupted
marker, andhealth-checkreports 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 withdatabase is locked,health-checkreports 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, readOKin the same
test. - A server killed before its first progress line, while its status says
starting, leaves no marker.health-checkreports the index empty; a
query then indexes only the files it reads and answers from them, and
health-checkreadsOK(flask:show url_forindexed 8 of 109 files and
saidSymbol 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 andhealth-checkfail as
in 0.170.1:index database is corrupt: file is 0 byteswhileindex.dbis
empty, thendatabase 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. similarfalls back to the bare name without the note, and--jsonand 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 untilincremental-indexor
doctorruns. impact Xcounts the excluded edges into every definition named X; MCP
get_ast_nodecounts them for the one it selected (flaskpost: 215 and
12).cargo auditreports RUSTSEC-2026-0367, -0368 and -0369 inzip7.2.0,
whichcandle-core0.9.2 requires for the embedding model. No candle
release takes the fixedzip9 yet (0.11.0 requires^8.6.0); candle reads
and writes archives and never calls theextract()the advisories concern.
0.170.1 ships the samezip.