feat(ruvnet-brain): distinct remediation for the reclaim-stuck refusal (ADR-0061) - #242
Merged
Merged
Conversation
forge-update's legacy-backup reclaim refuses "unresolved rollback state exists" forever once kb.bak-*/kb.install-preserved-* snapshots accumulate — verified 2026-09-27 that --update can never clear it, but `npx ruvnet-brain --uninstall` (which never touches those snapshots) followed by a fresh reinstall does. Records the decision to classify this refusal distinctly and give it accurate remediation text, without automating the workaround or adding new install-routing logic.
…ck refusal (ADR-0061) Adds BRAIN_RECLAIM_STUCK, a narrow pattern matching forge-update's legacy-backup reclaim refusal, alongside the existing BRAIN_REFUSAL/ BRAIN_CAUSAL patterns in heal.mjs. Held refusals of every other shape keep their existing generic remediation text unchanged. Adds legacySnapshotBytes(), a bounded, best-effort helper reporting the count/bytes of kb.bak-*/kb.install-preserved-* directories — folded into the reclaim-stuck row's remediation text as one sentence, not a new footprint-metrics subsystem (ADR-0025 stays the owner of that). brainReleaseRow() now gives a reclaim-stuck hold a distinct fix: run `npx ruvnet-brain --uninstall` then `ak sync`, which clears the version block via ak's existing pinned --force fresh-install branch (no new install-routing logic — updaterPresent() already reports false once kb/ is gone) but does not free the legacy-snapshot disk usage; cites stuinfla/ruvnet-brain#335. The remediation stays repair: 'manual', never auto-run — a forced fresh install can itself be refused for a Brain with private stores after downloading the whole bundle (#237 §4).
The reclaim-stuck remediation cites this thread in source comments (src/lib/heal.mjs, src/commands/status/sections/ruvnet-brain.mjs); the watch registry requires every cited thread to be registered.
…tall routing (ADR-0061) - a reclaim-stuck refusal is held exactly like any other refusal - status gives it distinct, actionable remediation (--uninstall, ak sync, #335) while every other held refusal is unaffected (regression guard on the existing generic-refusal fix text) - after --uninstall, installRuvnetBrain's existing routing (updaterPresent false, present true -> pinned --force --version) already takes the fresh-install path and clears the hold — pins that behavior, adds no new logic - legacySnapshotBytes(): missing root, non-matching dirs ignored, recursive byte sums across multiple snapshot dirs, and the bounded file-cap's exhausted/lower-bound path
… case (ADR-0061)
The generic row's advice ("fix the cause, then run --update") is wrong
for this specific refusal — there is no fix, and --update can never
clear it. Adds a distinct row naming the verified workaround and
stuinfla/ruvnet-brain#335.
…pshot dir was readable count > 0 with every dirBytes() call failing (permission denied, a dir removed mid-walk) left `bytes: dirs.length ? bytes : null` returning 0 — a false "nothing to reclaim" inside the row whose whole purpose is honest disk reporting. Track whether any dir actually contributed a real number instead of inferring it from the dir count.
…lint MD040) CI's quality check caught it: a fenced code block needs a language, even for a plain error message.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ak syncheld a RuvNet Brain refresh behind[forge-update] ERROR: unresolved rollback state exists; refusing to create another full-KB copy.— verifiedthat this is a permanent subtype of held refusal: forge-update's legacy-backup
reclaim (upstream issue #35) refuses to make another full-KB rollback copy
while old
kb.bak-*/kb.install-preserved-*snapshots remain, and--updatenever touches those snapshots, so retrying it (today's only remediation
advice) can never succeed.
BRAIN_RECLAIM_STUCK, classifying this refusal distinctly from everyother held refusal (which keep their existing, correct remediation text
unchanged — regression-guarded).
npx ruvnet-brain --uninstall(removes onlythe KB bundle, never the legacy snapshots) then
ak syncreinstalls fresh —verified live to clear the version block, because a fresh install takes a
different code path (
obtainBundle()) than--update(
forge-update.mjs --apply), which is the only one blocked by the reclaimcheck.
installRuvnetBrain()already routes this correctly oncekb/isgone (
updaterPresent()false → pinned--force --versionfresh install) —no new install logic, just a test pinning that existing behavior.
legacySnapshotBytes()(bounded, best-effort — only runs inside this rarebranch).
not the legacy snapshots (they're untouched throughout). Filed as
stuinfla/ruvnet-brain#335,
registered in the upstream-watch list.
repair: 'manual'like every other held refusal —a forced fresh install can itself be refused for a Brain with private
stores after downloading the whole bundle (issue Sync and advanced dashboard health audit: repeated repairs, ownership conflicts, private Brain update, false drift and source readiness #237 §4).
See ADR-0061 for the full
record.
Found but not fixed here
fixStatusline()can't bootstrap a fully-missingsettings.json.statusLinekey from scratch (only migrates between two known non-empty formats). Hit
this live when testing
--uninstall— it removes the Brain's ownstatusline entry, and both
ak statusandak sync --dry-runreported thestatusline as fine even though it was
nullon disk. Restored by hand thissession; worth its own issue, not folded into this PR.
Test plan
npm test— 5113 tests, 0 failures (6 pre-existing skips, unrelated)npm run lint— 0 errors (69 pre-existing complexity warnings, none intouched files)
unchanged generic-refusal text, post-uninstall install routing,
legacySnapshotBytes()(empty root, non-matching dirs, recursive sum,bounded file-cap exhaustion, unreadable-dir null-not-zero)
reinstalled, landed cleanly on 4.3.29,
search_ruvnetcame back with noClaude Code restart,
ak syncre-verified clean afterwardRefs stuinfla/ruvnet-brain#335
🤖 Generated with Claude Code