neuralmind.uk / Recover from a stale code graph Edit this page on GitHub ↗

Recover from a stale code graph

Best for: anyone whose repo has a committed graphify-out/graph.json, a graph built on another machine or OS, or a team that switched between graphify and the built-in backend — and anyone wiring NeuralMind into CI.

Primary goal: know within a second whether the index describes the code you have, and get back in step with one command when it doesn’t (v4.4.0+).

A code graph that lags the code is the worst kind of stale index: every query still “works”, it just answers from code that no longer exists and can’t see code that does. Before v4.4.0 that was silent. A graphify graph committed months ago, built on Windows, missing every module added since, still passed doctor.


1. Check: does the graph match the files on disk?

neuralmind doctor .

The Code graph check now compares the graph with the working tree in both directions, whatever tool built it:

  [FAIL] Code graph: graphify-out/graph.json (graphify, 2026-04-22, 214 commits behind HEAD), 3,197 nodes
         51 files on disk not in graph (lib/render/overlay.tsx, scripts/collect_stats.py, ...)
         3,176 node paths (319 files) use '\' separators (built on Windows)
         -> neuralmind build . --regenerate-graph

What each line means:

Finding Meaning Severity
files on disk not in graph new code the index can’t answer about WARN, FAIL above 10%
files in graph no longer on disk deleted or renamed code still being served WARN, FAIL above 10%
node paths use \ separators the graph was built on Windows; none of its paths line up with a POSIX checkout FAIL
files changed since the graph was built edited after the graph; from git diff for a committed graph WARN
commits behind HEAD how old a committed graph is; “age unknown: shallow clone” on a depth-1 checkout informational

The same report appears in neuralmind health (exit 1 when it isn’t OK), at the start of neuralmind build, and as one line at the top of your agent’s first neuralmind_wakeup call — so the agent knows not to trust the index before it answers from it.

2. Fix: regenerate with the built-in backend

neuralmind build . --regenerate-graph
Graph source: built-in (tree-sitter), 8,351 nodes. Ignoring graphify-out/graph.json (graphify, 2026-04-22).
Graph: .neuralmind/graph.json (built-in, regenerated, 8,351 nodes)
Build successful!
   ...
   Delta: +8,351 new, ~0 updated, =0 skipped, -3,197 removed

You often don’t need the flag: with the default graph_source: auto, a graphify graph that fails the freshness check is replaced by a built-in graph on the next build, and the build prints why.

3. Choose the graph source on purpose

.neuralmind.yaml:

graph_source: auto      # default: built-in graph if present, else graphify;
                        # a failing graphify graph is replaced at build time
# graph_source: builtin # always tree-sitter; graphify-out/ is never read
# graph_source: graphify # only graphify-out/graph.json — teams that run graphify in CI

NeuralMind never switches source silently. If the last build used the built-in graph and .neuralmind/graph.json disappears, the next build stops and names both ways out (--regenerate-graph, or graph_source: graphify) instead of quietly serving an old graphify graph. Every build prints which graph it used on a Graph: line, and neuralmind build-status shows it afterwards.

The graphify graph you upgraded past (v4.13.0+). Once the built-in graph exists, auto never reads graphify-out/graph.json again, but the file still looks like a live graph to anyone (or any script) inspecting the repository. Every build names it until you deal with it, and neuralmind doctor warns under Graph source:

  [warn] Graph source: builds read .neuralmind/graph.json; graphify-out/graph.json is unused (graph_source: auto picks the built-in graph once it exists)
         -> Archive it: neuralmind build . --archive-graphify (or set graph_source: builtin in .neuralmind.yaml to keep it)

--archive-graphify renames it to graphify-out/graph.json.archived-<timestamp> and leaves the rest of graphify-out/ alone; rename it back to undo. If you still run graphify for something else, set graph_source: builtin instead.

4. Gate it in CI

neuralmind health .            # 0 = in step, 1 = out of step (or nothing to check), 2 = no index
neuralmind build . --strict    # exits 3 before embedding when the graph FAILs

health used to fail whenever the index was older than 24 hours. It now fails only when the graph and the code disagree, so a nightly job stops rebuilding a current index and starts catching a stale one.

A watcher, and a grace period (v4.13.0+). When files change without commits, run neuralmind watch . --reindex and give it time to catch up:

# .neuralmind.yaml
stale_after: 30m
neuralmind health .    # 0 "catching_up" while behind for under 30m; 1 after
neuralmind doctor .    # Code graph FAILs once behind for over 30m: "behind for 3h, over stale_after 30m"

A cron job can gate on either without a monitor of its own. Doctor’s file lines now end with when the newest unindexed file changed (; newest modified 2h ago), so a file written during an incident stands out from months-old drift.


The potential use case: a pre-merge freshness gate

Because the check is stat calls and a few git commands — no parsing — it is cheap enough to run on every pull request. A team that commits its graph (or ships one in a container image) can fail the PR when the graph no longer covers the code under review, instead of finding out when an agent confidently describes a module that was deleted last sprint.

Related: Release notes v4.4.0 · Growing monorepo · Index any repo with just pip