Status at a glance
AST extractor crashes when tsconfig “extends” is an array
Still live. Reported in #206. See the issue for the report as it was filed. Issue #206 on the base repository.sync —ast hangs indefinitely on repeated targets (Windows), sync.exclude appears unused by the AST walker
Still live. Reported in #174. See the issue for the report as it was filed. Issue #174 on the base repository.sync —ast crashes on pnpm-workspace.yaml with ‘packages: - .’
Fixed in 0.16.0. Reported in #172. See the issue for the report as it was filed. Issue #172 on the base repository.base sync: sync.exclude is bypassed by a symlink under an allowed path (v0.15.2)
Fixed in 0.16.0. Reported in #164.sync.exclude is applied to the path as walked, not to the resolved target. A symlink placed under an allowed path that points into an excluded directory is followed, and the excluded content is extracted into the graph.
People use sync.exclude to keep private or credential material out of the graph, so a single stray symlink silently breaks that boundary.
base sync: Obsidian [[wikilinks]] in frontmatter related: produce invalid IRIs, and one bad file aborts the whole sync (v0.15.2)
Fixed in 0.16.0. Reported in #162.base sync aborts the entire sync when one Markdown file has Obsidian-style wikilinks in a frontmatter related: list:
"[[Note]]", quoted), so Obsidian vaults hit it almost immediately. In our vault, 60% of notes (6,559 of 11,021) use it, so the vault cannot be mapped at all.
Two separate problems:
- Parsing. Block-list items are joined, then
parse_liststrips[/]only from the outer ends of the joined value. With two items,[[alpha]], [[beta]]becomesalpha]]and[[beta, and the leftover brackets end up inrelatedToIRIs (src/extract/frontmatter.rs). - Failure scope. The
?onstore.update(...)inextract::sync(src/extract/mod.rs) exits the whole file loop beforelocked.write. One bad file stops every file, and good files processed earlier in the batch are not persisted. In an empty workspace,base doctor --jsonthen reportshealthy: truewithtiers: [].
.base/, create note.md:
base sync. Result: exit 1 with the error above. Change the values to alpha and beta and the result is Sync complete: 1 scanned, 1 extracted, 0 skipped. A single wikilink item happens to pass; two items expose the outer-only bracket trimming.
What to do. Rewrite related: values to bare stems. For an Obsidian vault that breaks property links in Obsidian, so it isn’t practical at scale.
Issue #162 on the base repository.
base scaffold writes an invalid TOML path on Windows and breaks base.toml parsing
Fixed in 0.15.2. Reported in #158. See the issue for the report as it was filed. Issue #158 on the base repository.doctor reports a tier HEALTHY while it carries foreign-graph quads (split from #112)
Fixed in 0.15.0. Reported in #142. See the issue for the report as it was filed. Issue #142 on the base repository.ast: the corpus precondition probe cannot see a missing PyYAML, because sorted(FIXTURES) picks the one .md fixture with no frontmatter fence
Not fixed. Closed as not planned: the gap is in the code map’s own test, and base works the same for someone using it either way. Reported in #134. See the issue for the report as it was filed. Issue #134 on the base repository.ast: every ‘warning: skipped’ notice is discarded on all four background refresh paths (missing the ’# Skipped ’ prefix the recorded channel gates on)
Not fixed. Closed as not planned: a file the code map skips because it could not be parsed is still not reported to the session. Reported in #133. See the issue for the report as it was filed. Issue #133 on the base repository.relay: the .watching sentinel proves a file was touched, not that the titleholder is watching
Still live. Reported in #132. See the issue for the report as it was filed. Issue #132 on the base repository.ast: a missing PyYAML silently drops every markdown frontmatter relation (bare except Exception swallows the ImportError)
Fixed in 0.15.0. Reported in #131. See the issue for the report as it was filed. Issue #131 on the base repository.automap: is_under compares an 8.3 short path against its long form, because canonicalize only expands a path that exists
Fixed in 0.16.0. Reported in #128. See the issue for the report as it was filed. Issue #128 on the base repository.store: the atomic rename in write_back_inner has no retry, and NTFS contention makes it fail
Fixed in 0.15.0. Reported in #127. See the issue for the report as it was filed. Issue #127 on the base repository..gitattributes does not pin *.rs, so a Windows-written Rust file commits with CRLF
Still live. Reported in #125.
See the issue for the report as it was filed.
Issue #125 on the base repository.
Lock tripwire’s session_start.rs “sentinel” is an exemption: it guarantees the tripwire will NOT fire there
Not fixed. Closed as not planned: the gap is in the repository’s own lock test, and base works the same for someone using it either way. Reported in #121.tests/lock_tripwire_test.rs lists src/hook/session_start.rs in its allow-list, and both the
test’s own reason string and the fork doc describe that entry as a sentinel: the fork doc’s
Definition of Done says it is “listed so the tripwire fires if one ever appears, in the file where
an unlocked write would be worst because session start runs on every session of every workspace”,
and adds “do not delete it as dead weight.”
The entry does the exact opposite. ALLOW / ALLOW_FILES is an EXEMPTION list, checked with
if allowed.contains(rel) { continue }. Being on it guarantees the tripwire never fires for
that file. A new unlocked graph writer in session_start.rs is silently permitted, and the entry
that is supposed to catch it is what permits it.
Measured as a pair, so the cause is not inferred:
The control is what makes this evidence rather than a guess (law 24): with the entry removed the
rule does catch the very same injection, so the green in M2 is caused by the exemption and not by
the rule failing to see the code. Both arms compiled and both ran the test binary — checked, so
neither is a void result.
From a clone at
610636e (or this branch):
session_start.rs has no production graph write, so nothing is currently
mis-permitted. The exposure is entirely about the future write the entry was placed to catch.
Found by condor during the 0.14.3 round while mutation-testing the widened tripwire (#104). Filed to 0.14.4 unranked per the round charter; nothing is added to 0.14.3 mid-round. This is the same class the round keeps turning up — prose asserting a guarantee the code does not implement — and it sits inside the very instrument #104 widens, one line away from the comment in
functions() that claimed to skip test modules and did not.
Issue #121 on the base repository.
AST: extract_verilog yields only a file node — module_declaration has no ‘name’ field in tree-sitter-verilog 1.0.3, so no module, function, task or instantiates edge is ever emitted
Not fixed. Closed as not planned: Verilog files still give no modules, functions or tasks in the code map. Reported in #120. See the issue for the report as it was filed. Issue #120 on the base repository.AST: the PHP helper-call branch reads only single-quoted arguments, so config(“a.b”) emits nothing while config(‘a.b’) emits an edge
Fixed in 0.15.0. Reported in #118. See the issue for the report as it was filed. Issue #118 on the base repository.Two markdown extraction paths read different frontmatter keys for ops:relatedTo, and a block-sequence relatedTo: emits nothing on either
Fixed in 0.15.0. Reported in #115.
base has two markdown frontmatter extraction paths and they read different keys for the same ops:relatedTo predicate. No spelling works in both, and the spelling base’s own published contract teaches produces zero edges on the primary path.
base sync— Rust,src/extract/frontmatter.rs:49— matches the lowercased key"relatedto".base sync --ast— Python,scripts/ast/extractor.py:6127-6129— readsfrontmatter.get("related"), gated onontology: true.- The shipped contract
docs/markdown-ontology-protocol.md:35documentsrelatedonly, and all three of its worked examples (:88,:102,:120) write it as a YAML block sequence.
base sync run in an isolated workspace, and four direct extract_markdown() calls:
Three consequences, in order of how quietly they bite:
relatedTo:written as an ordinary YAML block sequence emits nothing on either path. The Rustparse_listaccepts only the inline[...]form; every fixture infrontmatter.rs(:439,:601) is inline, so nothing in the suite covers a block sequence. A user writing perfectly valid YAML gets silence from both extractors.- The contract spelling is inert on the primary path. A document written exactly as
docs/markdown-ontology-protocol.mdinstructs contributes zeroops:relatedTotriples tobase sync’s graph. - The two graphs disagree by construction. The same repository synced both ways yields different
relatedToedge sets, with no warning from either path.
ops:documentType "spec", ops:name, ops:path, ops:hasSection and ops:lastExtracted in the graph, proving the frontmatter parser reached the file and parsed its other keys. The only missing triples are the relatedTo ones. In the run above, the four probe documents produced 31 graph lines and exactly 2 relatedTo triples, all from the one inline-relatedTo: arm.
What to do. Write relatedTo: with an inline list (relatedTo: [a, b]) to get edges from base sync, and related: with either form plus ontology: true to get them from base sync --ast. Both keys must be present to populate both graphs. There is no single spelling that works everywhere.
<sub>Found by
raven during the 0.14.3 round while re-measuring #107’s relation census against the tree. #107 states that RELATION_MAP “carries one key nothing emits: relatedTo” — on the tree, extractor.py:6129 does emit it, which is what led here. Filed to 0.14.4 per the round charter; not added to 0.14.3.</sub>
Issue #115 on the base repository.
rule -g list reads the whole global file, rule -g remove writes to one named graph, and the mismatch reports success
Fixed in 0.15.0. Reported in #112.
See the issue for the report as it was filed.
Issue #112 on the base repository.
ast: the serializer silently drops 17 of the 24 relations the extractor emits (zero ops:inherits triples exist in any map)
Fixed in 0.15.0. Reported in #107. See the issue for the report as it was filed. Issue #107 on the base repository.AST: the JS import path records unresolved imports as external symbols, and markdown headings are emitted as fn entities
Fixed in 0.15.0. Reported in #105. See the issue for the report as it was filed. Issue #105 on the base repository.match_commands activates star commands from quoted text anywhere in the prompt
Fixed in 0.15.0. Reported in #101. See the issue for the report as it was filed. Issue #101 on the base repository.verification: ast_followups_0142.sh accepts a BASE_SHA from the wrong era and fails loudly for the wrong reason
Not fixed. Closed as not planned: the gap is in a 0.14.2 release check script, and base works the same for someone using it either way. Reported in #100. See the issue for the report as it was filed. Issue #100 on the base repository.AST: a synthesised external symbol is emitted once per reference and collides onto one IRI, so the app-root counter over-reports
Fixed in 0.15.0. Reported in #98. See the issue for the report as it was filed. Issue #98 on the base repository.install.ps1 is not safe under stderr redirection on Windows PowerShell 5.1
Fixed in 0.14.2. Reported in #96. See the issue for the report as it was filed. Issue #96 on the base repository.Every .sh checks out CRLF on a Windows clone (no .gitattributes), so the shebang breaks and script hashes differ across platforms
Fixed in 0.14.2. Reported in #95. See the issue for the report as it was filed. Issue #95 on the base repository.base installed before Claude Code is permanently inert: hooks need an existing ~/.claude and the self-heal runs from a hook
Fixed in 0.15.0. Reported in #93. See the issue for the report as it was filed. Issue #93 on the base repository.base install finds scripts/ast only via CWD when run from an unpacked release archive
Fixed in 0.15.0. Reported in #92.
See the issue for the report as it was filed.
Issue #92 on the base repository.
Windows: base install writes ~/.local/bin/base with no .exe, so a fresh install cannot be run by name
Fixed in 0.14.2. Reported in #91.
See the issue for the report as it was filed.
Issue #91 on the base repository.
AST: notices from a background refresh reach nobody; persist them for session start
Fixed in 0.14.2. Reported in #89. See the issue for the report as it was filed. Issue #89 on the base repository.10 graph writers still write unlocked (bulk/maintenance sites)
Fixed in 0.15.0. Reported in #87. See the issue for the report as it was filed. Issue #87 on the base repository.relay ping: two pings in the same millisecond collide on one slug, and the earlier one is silently replaced
Fixed in 0.14.2. Reported in #86. See the issue for the report as it was filed. Issue #86 on the base repository.AST: .vue is in the dispatch table but extract_js cannot read a single-file component
Fixed in 0.14.2. Reported in #84. See the issue for the report as it was filed. Issue #84 on the base repository.AST: LANG_MAP is a third extension list and has drifted from the parser table
Fixed in 0.14.2. Reported in #83. See the issue for the report as it was filed. Issue #83 on the base repository.AST: file membership walks containment one hop, so nested entities are attributed to the app root
Fixed in 0.15.0. Reported in #82. See the issue for the report as it was filed. Issue #82 on the base repository.release assets carry no license file: the tarball packs the binary and scripts/ only
Fixed in 0.14.2. Reported in #80. See the issue for the report as it was filed. Issue #80 on the base repository.Hook event log lands in the tier of the process cwd, not the payload’s cwd (the line names workspace A and sits in workspace B)
Fixed in 0.14.2. Reported in #77.
See the issue for the report as it was filed.
Issue #77 on the base repository.
base hook stop prints the relay task nudge as plain stdout, which Claude Code never delivers to the model on Stop
Fixed in 0.14.2. Reported in #76.
See the issue for the report as it was filed.
Issue #76 on the base repository.
base hook post-tool-use prints its context as plain stdout, which Claude Code never delivers to the model on PostToolUse (section AST context and extension nudges are silent no-ops)
Fixed in 0.14.2. Reported in #75.
See the issue for the report as it was filed.
Issue #75 on the base repository.
fork archive leaves no row: after base fork archive <slug> printed archived, the slug is absent from base fork list (neither open nor archived)
Fixed in 0.14.2. Reported in #74.
See the issue for the report as it was filed.
Issue #74 on the base repository.
base fork create then archive in one invocation reports success and leaves no row at all
Fixed in 0.14.2. Reported in #73. See the issue for the report as it was filed. Issue #73 on the base repository.fork archive from a workspace prints ‘archived’ and changes nothing when the fork lives in the global tier (#55’s shape on the fork/handoff registries)
Fixed in 0.14.2. Reported in #72. See the issue for the report as it was filed. Issue #72 on the base repository.base handoff create does not archive the prior open handoff, though its help says it does
Fixed in 0.14.2. Reported in #71.base handoff create documents itself as archiving the prior open handoff for the project, in both the group help and the subcommand help:
Commands:
create Register a handoff doc (archives any prior open handoff for the project)
It does not. Registering a second handoff for the same project leaves the earlier one open, so both resurface at session start.
Issue #71 on the base repository.
Extensions parsed but absent from _FILE_EXTS lose file attribution on every platform (plus a Windows-only .baseignore bug)
Fixed in 0.14.2. Reported in #66.
See the issue for the report as it was filed.
Issue #66 on the base repository.
query_domain_from_graph never marks a neighbourhood record as served: the SPARQL projects ?name ?type, not ?related
Fixed in 0.14.2. Reported in #65. See the issue for the report as it was filed. Issue #65 on the base repository.base domain sync reports “0 rules” for a domain with five, because it counts only the rules it imported
Fixed in 0.14.2. Reported in #62.base domain sync reports a rule count that reads as “how many rules this domain has”, but counts only the rules it wrote into the graph from domains.toml. With five rules present, added by base rule add, it says:
$ base domain sync
Domain sync complete: 1 domains, 0 rules, 0 decisions
Zero is literally what sync did. CLI-added rules are written at rule/<slug>/cli-N in their own IRI namespace, on purpose, so that a base sync rebuilding a domain from domains.toml cannot delete them. Sync correctly did not touch them, then reported a number that looks like it counted them.
Issue #62 on the base repository.
A missing graph.nq is an error rather than an empty tier, so a fresh tier cannot take its first rule
Fixed in 0.14.1. Reported in #60. On a tier whosegraph.nq does not exist yet, rule commands fail hard instead of reading the tier as empty:
$ base rule list -g
Failed: Failed to open /home/…/.base-gbl/graph/graph.nq
rule add fails the same way, in either tier, so a tier that has never been written to cannot take its first rule — there is no command that creates the file, and the command that would write to it refuses first.
An empty graph.nq works fine. Tested specifically. The entire gap is between “no file” and “empty file”.
(mktemp -d) # a home with no global graph
cd /tmp # outside any workspace
base rule list -g
Failed: Failed to open …/graph.nq
$ base rule add —domain anything —text “first rule” —global
Failed: Failed to open …/graph.nq
BASE_HOME/.base-gbl/graph && touch base rule list -g # same tier, now with an empty file
(no rules)
Issue #60 on the base repository.
rule remove reports success when the domain has no rules in the targeted tier
Fixed in 0.14.1. Reported in #55.
See the issue for the report as it was filed.
Issue #55 on the base repository.
”base rule list” shows one tier, the hook injects both
Fixed in 0.14.1. Reported in #53. A domain can carry rules in both the workspace graph and the global graph.base rule list --domain X shows only the workspace rules, base rule list -g --domain X only the global ones, and neither mentions that the other set exists. The UserPromptSubmit hook injects the union. So a rule deleted with base rule remove keeps arriving in every prompt, and no CLI output explains why.
With the same domain present in both tiers:
base rule list -g —domain myapp
[myapp] 5 rules:
The next prompt then arrives carrying 7 rules for that domain, including one that was removed from the workspace tier.
Issue #53 on the base repository.
base domain remove/create act on the global tier while you are standing in a workspace
Fixed in 0.14.1. Reported in #52.base domain remove <NAME> reports “not found” for a domain that exists, and exits 0. base domain create --name <name> then reports “created” without writing anything to domains.toml. Presumably both are case-folding against the existing entry, but the messages say the opposite of what happened.
With a domain named MyApp in domains.toml:
echo base domain create —name myapp —path .
Domain ‘myapp’ created
$ grep ‘^name’ .base/domains.toml
name = “MyApp”
The help text for remove states the name argument is case-insensitive.
Issue #52 on the base repository.
base update never refreshes the installed CLAUDE.md contract
Fixed in 0.13.18. Reported in #45. See the issue for the report as it was filed. Issue #45 on the base repository.sync —repair prints every repair as applied, then discards all of them
Fixed in 0.13.18. Reported in #44. See the issue for the report as it was filed. Issue #44 on the base repository.graph analyze/query/neighbors/path/get-node cannot see the edges base writes for itself
Fixed in 0.13.18. Reported in #42. See the issue for the report as it was filed. Issue #42 on the base repository.base dashboard silently discards every CLI write made after the server started
Fixed in 0.14.1. Reported in #41.
See the issue for the report as it was filed.
Issue #41 on the base repository.
Stop-hook refresh of an existing map bypasses the size fuse: a 25k-file markdown workspace re-indexes for 10 minutes after every turn, and Claude Desktop hangs on Stopping until it finishes
Fixed in 0.14.1. Reported in #40. Two things, one cause. 1. A large workspace with an existing map re-indexes in full after every turn. The Stop hook returns in ~150 ms, but it spawnsbase sync --ast --yes --target <workspace>, which runs python onto_ast.py <workspace> --full --out ... --confirm. On a markdown-heavy workspace of 25,166 files that Python run takes ~10 minutes of CPU per turn, rewrites a 281 MB ast.ttl, and the next turn starts another one (debounce is 20 s; turns are further apart than that). The map it produces is 285,040 ops:Function entities of which almost all are markdown headings (extract_markdown emits one node per heading; the workspace is a content shop, not code). Nothing queries the map.
The 2,000-file prompt in onto_ast.py never fires because the hook passes --yes, which becomes --confirm. The 0.13.16 fuse (FUSE_SOURCE_FILES = 5_000) would refuse this tree on a first build, but automap.rs:195-198 applies it only when plan == MapPlan::Build; any map that already exists (built on 0.13.10-0.13.15, or by a hand base sync --ast --yes) is refreshed unconditionally forever.
Measured on this machine (file counts exclude node_modules and .git):
Process tree during one refresh (Get-CimInstance):
spawn_sync sets Stdio::null() for all three streams, so this is not the spawn arguments; it is Windows handle inheritance. Rust’s Command on Windows spawns with bInheritHandles = TRUE and no handle allow-list, so every inheritable handle in the hook process, including the stdout pipe the harness gave it, is duplicated into base sync and from there into Python. The harness waits for EOF on that pipe. Measured from Git Bash, which is the same pipe shape:
cat returns only when the spawned onto_ast.py --full --confirm finishes, and .base-ast\ast.ttl is rewritten on every repeat of the last line more than 20 s apart. In a Claude Code session opened in that folder, every turn ends with “Stopping…” for the length of the build.
What to do. Remove the base hook stop entry from ~/.claude/settings.json (the other four hooks stay; ensure_hooks_wired does not re-add it until the next version’s first run because of the .hooks-wired-<version> marker). Maps go stale, which costs nothing in a workspace that never queries them. Deleting .base-ast/ is not a workaround: first contact rebuilds it, and on 0.13.14 the rebuild is unattended.
Issue #40 on the base repository.
base domain get reports Rules (0) for rules that exist and are injecting
Fixed in 0.13.17. Reported in #38.
base domain get <name> reports Rules (0) for a domain whose rules exist and are being injected. base rule list --domain <name> shows them, and the PreToolUse hook renders them into the session, so only this one reader is wrong.
The two commands read different stores. base rule add writes the rule into the graph at rule/<slug>/cli-N, deliberately in its own IRI namespace so a base sync cannot clobber a CLI-added rule (src/crud/rule.rs: “rules are managed by editing domains.toml (sync GC handles them)”). base domain get prints d.rules.len() from the Domain struct, which is deserialized from domains.toml alone (src/domain/mod.rs:448). A CLI-added rule never appears in that file, so the count is structurally always zero for them.
rule list, not with domain get:
base rule list --domain <name>. It reads the store the hook reads.
Issue #38 on the base repository.
The AST auto-mapper adopts the OS temp directory and rebuilds it on every hook
Fixed in 0.13.16. Reported in #32. The AST auto-mapper treats any folder a hook touches as an app. A shellcd into %TEMP% (or any hook contact with a path under Temp, AppData, a cache, node_modules, or the other OS’s home) adopts that folder, plants .base-ast/ in it, and every later hook refreshes the map: a thirty-minute extraction of the temp directory, for ever, on every Stop hook. Measured on the author’s machine 2026-09-03 (the 11:43 loop). Nothing capped the size of a tree built unattended.
- In a Claude Code session with base hooks installed, run
cd %TEMP%(orcd /tmpunder WSL with a marked folder inside). - Watch
%TEMP%\.base-ast\appear and the extractor pin a core. - Every later hook refreshes it.
%TEMP%\.base-ast* and do not cd into Temp from a hooked shell. Fixed in 0.13.16.
Issue #32 on the base repository.
Every graph write costs seconds of kernel CPU, and every prompt pays it twice
Fixed in 0.13.16. Reported in #31. Every graph write serialises the whole graph through an unbuffered writer, one syscall per term. Measured on a copy of a real global graph on NTFS: 6.11 s of kernel CPU per write, and abase learn or base decision log sits at the prompt for that long.
Every prompt pays it twice: the domain-sync guard “touches” .domain-sync-ts with a zero-byte write, which does not advance NTFS LastWriteTime on an existing empty file, so the guard never closes and the UserPromptSubmit hook re-syncs both tiers on every prompt. Measured: 14.27 s per prompt hook.
- On Windows with base hooks installed, run
base learn --text probe --domain probeand watch kernel time (Task Manager, orC:\Users\Chris\.base-gbl\scripts\probe-write-cost.ps1). - Send two prompts in a row without touching
domains.toml; time the UserPromptSubmit hook. The second prompt re-syncs anyway. stat .base-gbl/.base/.domain-sync-ts: 0 bytes, mtime months old.
base rule add gives every rule after the tenth the same number (string MAX on :index)
Fixed in 0.13.17. Reported in #29.
Every rule added after the tenth in a domain gets the same number, 10, and lands on the same IRI rule/<domain>/cli-10. base rule list looks fine (it shows 25 rules), but the hook injection renders the cross-product of the collided rules’ texts and rationales: a domain with 25 rules injected ~200 numbered lines, most of them the same text repeated with every other rule’s rationale.
Cause: next_rule_index in src/crud/rule.rs runs SELECT (MAX(?idx) ...) over :index literals that are stored as plain strings, so once "0".."9" exist the max is "9", the next index is 10, and it stays 10 forever. The graph confirms it:
ORDER BY ?pri in src/domain/query.rs has the same string-vs-number problem (10 sorts before 2), which is only cosmetic.
prompt_keywords = ["probe"] to the probe domain in .base/domains.toml and base domain sync first, for the last line.)
What to do. Patch: cast to integer in both queries.
src/crud/rule.rs, and ORDER BY xsd:integer(?pri) in src/domain/query.rs. Verified: 12 rules get 12 numbers, and an existing collided graph is fixed by deleting the domain’s rule quads and re-adding. Happy to open a PR if you want it.
Issue #29 on the base repository.
A release aborts after the version bump: the coach is regenerated before the changelog is written
Fixed in 0.13.16. Reported in #27.scripts/release.sh <version> --push bumps Cargo.toml, README.md and Cargo.lock, then dies before it commits or tags:
Cargo.toml and the coach is stamped to it, but there is no commit, no tag and no release.
changelog_has_a_section_for_this_version is one of the help_docs tests, so BASE_REGEN_DOCS=1 cargo test --bin base help_docs runs it. It is also the one check BASE_REGEN_DOCS deliberately cannot satisfy, by design: a missing changelog entry is a release-process failure rather than drift to paper over. Regenerating the coach before writing the changelog therefore fails on the single check regeneration cannot fix, and set -euo pipefail aborts.
scripts/test-release-invocations.sh cannot catch this. It runs the command lines the release assembles, one at a time, and each of them runs correctly. The defect is in the order, not in any step.
From a clean main at v0.13.15:
git status then shows Cargo.toml, Cargo.lock, README.md and the three claude/skills/base-help/references/*.md files modified, with no commit and no tag.
Issue #27 on the base repository.
hook-events.jsonl growing unbounded
Fixed in 0.14.1. Established from source at 4866996.
Before 0.14.1, every hook invocation appended a line to hook-events.jsonl, and the only trimming (over 10 MB, keeping the last 5,000 lines) ran from the dashboard, so on a machine that never opened base dashboard nothing trimmed the file.
Since 0.14.1 the hook writer trims the file itself before every write, with the same limits, whether or not the dashboard is ever opened.
What to do. Update to 0.14.1 or later. Nothing depends on the file’s history; it is a debugging trail, not state.
Issue #22 on the base repository.
-g writing to the wrong tier
Fixed in 0.13.3. Established from source at 4866996, which is the commit that fixed it.
--global swapped the working directory for ~/.base-gbl and then searched upward for the nearest .base/. Nothing creates ~/.base-gbl/.base at install time, so on a machine that had only run base install, the search climbed past the tier to the home directory and took <home>/.base instead, which is a workspace tier. Every -g command then reported success against the wrong graph.
It was not specific to one command or one platform. base learn -g escaped the same way as base graph apply-ops --global.
The global tier now resolves to its one fixed location directly and skips the search, because a tier with exactly one location cannot meaningfully be “not found”. The workspace tier keeps the walk, and keeps refusing when the walk finds nothing.
What to do. Update. If you ran -g commands on an earlier 0.13.x, check <home>/.base/graph.nq for entries you meant to be global.
Issue #21 on the base repository.
Hooks never show an error
By design, and still true. Since 0.14.1,base doctor reports a hook that failed, and session start says so while hooks are still failing. Established from source at 4866996.
All five hook events fail open. Any internal error is written to stderr, and the process exits 0 with empty stdout, so a broken graph or a failed query never blocks your session.
The cost is that a broken hook looks exactly like a hook that had nothing to say, and your agent host will usually not show you hook stderr. Silent breakage can persist for a long time.
What to do. Do not infer failure from missing output. Instead:
hook-events.jsonl is the audit trail, with the event, whether it succeeded, and how many domains matched. base doctor is the one surface that deliberately does not stay quiet: it reports config files that exist but cannot be parsed, and exits nonzero.
There is no complete list of every place base swallows an error, and this page will not invent one. The design is the answer: assume any hook can fail silently, and use the four commands above rather than reasoning from what you did or did not see.
Issue #20 on the base repository.
An empty result outside a workspace
Fixed in 0.14.1. Reproduced with read-only commands on 0.13.2. Some commands fail loudly outside a registered workspace, and some quietly answer from the global tier alone. The inconsistency is the problem, because an empty answer from the wrong directory is indistinguishable from an empty graph.project list errors. recall returns “No results found.” and means nothing by it. base doctor reports the global tier only, and base commands list shows only globally-scoped commands.
What to do. Run base project list first when a result surprises you. If it errors, you are outside a workspace and every other command’s “empty” answer is suspect. Then cd into the right workspace, or run base scaffold to register the one you are in.
Issue #19 on the base repository.
Domain triggers you cannot remove
Fixed in 0.14.1. Established from source at4866996.
The four base domain write commands do not agree on which tier they operate on. add-trigger resolves the workspace and writes {workspace}/.base/domains.toml. create, remove and remove-trigger ignore where you are standing entirely and only ever touch the global ~/.base-gbl/domains.toml.
So a trigger added inside a workspace cannot be removed from the command line. remove-trigger reports the domain is not found while base domain list keeps showing it, because reads merge both tiers and writes do not.
What to do. Hand-edit {workspace}/.base/domains.toml and delete the [[domain]] block, or the offending entry in its prompt_keywords or paths array. If you want a domain to stay manageable from the command line, create it with base domain create first, which puts it in the global tier where the remove commands can reach it.
This verdict is source-grounded rather than reproduced. Reproducing it needs a mutating command, and running one against live tiers to demonstrate a bug is not a trade worth making. The three write functions take the current directory as an argument and never read it;
add_trigger is the only one that resolves a workspace.Hardcoded hook output instructs the AI agent to bypass user consent (“RELAY WAKE CONTRACT”)
Fixed in 0.13.3. Reported in #13. See the issue for the report as it was filed. Issue #13 on the base repository.install writes bundled skills straight through a symlinked ~/.claude/skills
Fixed in 0.13.3. Reported in #12. See the issue for the report as it was filed. Issue #12 on the base repository.Relay wake-nudge text hardcodes “Chris” instead of a generic label
Fixed in 0.13.3. Reported in #11. See the issue for the report as it was filed. Issue #11 on the base repository.relay send writes to the workspace store, but the wake monitor watches ~/.base-gbl/relay-inbox — pings never arrive
Fixed in 0.13.0. Reported in #9. See the issue for the report as it was filed. Issue #9 on the base repository.Writes outside a registered workspace silently succeed but discard data; star commands leak to global instead
Fixed in 0.13.0. Reported in #8. See the issue for the report as it was filed. Issue #8 on the base repository.The update banner nagging on a current binary
Fixed in 0.12.0. Established from source at4866996.
The banner used to compare the version recorded in manifest.toml against the latest release, never against the binary you were running. Swap the binary by hand and the manifest went stale and nagged forever, and the stale value blocked the check that would have corrected it.
Session start now reconciles the recorded version against the running binary’s own compiled-in version before anything reads the manifest, and drops the pending-update string when the version changes. It self-heals, and it handles a rollback the same way.
If you still see it on an older binary. Correct version under [components.base] and clear pending_update under [update_check] in ~/.base-gbl/manifest.toml by hand. That file is plain config rather than the graph, so editing it is fine. base --version is always the truth about what is running.
Issue #7 on the base repository.
The “AST graph not yet populated” nag
Fixed in 0.12.0. Established from source at4866996.
The pre-tool-use hint used to hand-join the legacy {workspace}/.base/ast.ttl path, while maps are written to the {app}/.base-ast/ sidecar. For any workspace mapped after that migration the check could never pass, so the hint fired on every search command and base sync --ast never silenced it. Queries were fine throughout, which is exactly why the hint contradicted a populated base ast list.
The check now resolves the same way queries do, sidecar first and legacy second, so both satisfy it.
Issue #6 on the base repository.
base commands import corrupting commands.toml
Fixed in 0.12.0. Established from source at 4866996.
The importer used to write TOML by hand and escape only double quotes, so any rule containing a newline, tab or backslash produced a file that could not be parsed back. The loader then discarded the parse error and returned an empty command list, which is why “Imported 8 command(s)” and “No commands configured” could both be true in the same minute.
The writer now serializes through the TOML library and round-trips the output back before writing, and refuses outright when the existing file does not parse rather than appending onto damage. There is a regression test named for the issue that puts newlines, tabs, backslashes and a trailing quote through serialize, write and read.
base doctor also now reports a commands.toml it cannot parse and counts it against health.
Practical upshot. base commands import is safe. If star commands go quiet, run base doctor first; it will name a corrupt config instead of leaving you guessing.
Issue #5 on the base repository.
Next
Debugging injection
The tools these entries keep pointing at.
Updating
Getting onto the version these verdicts describe.