Skip to main content
That is the whole first answer. It reads both tiers, reports on each, and exits nonzero when something is wrong, which makes it usable in a script as well as by eye.

What healthy looks like

Each tier gets its own line: the graph file’s path, its line count, its size, and confirmation that it ends with a newline. Then an overall verdict. Why the newline check matters. The graph is a line-oriented text format. A file that does not end cleanly is a file something was interrupted while writing, and that is the shape almost every corruption takes. Next rung. base doctor --json gives the same report machine-readable. Pair it with base commands list, base project list and base ast list when you want a full picture: healthy graph, star commands loaded, workspace registered, code maps present. Advice that does not change the verdict. Since 0.16.0 doctor also reports, as advice: broad path triggers with the command to narrow them; what each hook printed against its budget and what it held back; whether the budgets were measured on the Claude Code you run; project next steps older than 14 days; and a usage section from the match log (rules that never fire, domains that fire on more than a quarter of your prompts, rules served and then corrected anyway, decisions served often but not reviewed in a long time, the rules held back most often and the longest ones). A trigger that cannot fire at all still counts against health. The one case that needs a person. If you renamed a workspace folder after base wrote data for it, its older entries stay under the old folder name. Doctor reports it with the reason and leaves it alone, because only you can say whose data it is.

Repairing

Why it works. It quarantines the lines it cannot parse and atomically rewrites the good set, taking a snapshot before it does. Reads elsewhere in base use a lenient parser on purpose, so one bad line degrades your context rather than blanking it; --repair is what turns that degraded state back into a clean one. The gotcha. Repair is not a substitute for knowing what went wrong. If a graph goes unhealthy repeatedly, something is writing to it that is not base.

Fixing what doctor reports

--fix prints a plan and changes nothing. --fix --yes applies it, after a snapshot of each graph. It is the same repair the 0.16.0 update runs once by itself:
  • entries that belong to another workspace move to that workspace’s graph, or, when base cannot find it, to .base/foreign-<name>.nq beside this one, so nothing is lost;
  • a correction that names exactly one record is linked to it, and the rest stay corrections;
  • supersession disagreements (one side says it replaces the other, the other does not agree) are settled;
  • the old [signal] max_chars setting is removed;
  • each graph is compacted, and its backups are cut to [graph] keep_backups (3).
Run it again and it plans nothing. base graph migrate --yes applies the same repair.

Rolling back

Bare --restore lists the snapshots available. Naming one restores it. Since 0.16.0 it also restores the config backups an update leaves, <name>.toml.BAK-<date>-pre-<version>, and every update line prints the exact --restore command for its own backup. It refuses any other file, so it cannot overwrite something base did not write.

Where the backups are

Every repair, restore, compact and purge snapshots the graph first, as a sibling file named graph.nq.bak-<operation>-<timestamp>. Since 0.16.0 the newest three are kept per tier ([graph] keep_backups), and older ones are rotated out. Before, it was ten, which came to 708 MB on one measured machine. Why it works. This is one backup path shared by every mutating operation, rather than each command inventing its own. Verified from source at 0ace1ba, the commit the shipped 0.15.2 binary was built from. The gotcha. Three is not many on a busy machine. Raise graph.keep_backups if you want more, and if you want to keep a particular state, copy that snapshot somewhere else; it will rotate out. Backups you made by hand under other names are left alone.

Maintenance that runs on its own

Tier graphs auto-compact at session start once they pass a size threshold, and a cooldown stops that from happening repeatedly.
Defaults: on, 12MB, and 24 hours between compactions. The gotcha. Compaction rewrites the graph. It snapshots first and it is atomic, but if you are debugging something that depends on the exact byte layout, turn graph.auto_compact off for the duration rather than being surprised by it.

Manual maintenance

compact deduplicates and canonicalizes. purge --stale removes notes that have gone unread past a threshold, and previews until you pass --apply. The gotcha. The purge clock is recency of reading, not age. A note’s lastRead is stamped every time base recall returns it, so this removes what you have not needed rather than what is old. See Decisions and recall.
Never hand-edit graph.nq. base’s writes are atomic, they snapshot first, and they refuse to run against an already-unhealthy graph. A text editor does none of those things, and an interrupted manual edit is how graphs get corrupted in the first place.

Next

Debugging injection

When the graph is fine and things still are not arriving.

Setup reference

Every base doctor flag.