Skip to content

feat(ruvnet-brain): distinct remediation for the reclaim-stuck refusal (ADR-0061) - #242

Merged
pacphi merged 7 commits into
mainfrom
feat/adr-0061-brain-reclaim-remediation
Sep 27, 2026
Merged

pacphi merged 7 commits into
mainfrom
feat/adr-0061-brain-reclaim-remediation

Conversation

@pacphi

@pacphi pacphi commented Sep 27, 2026

Copy link
Copy Markdown
Owner

Summary

ak sync held a RuvNet Brain refresh behind [forge-update] ERROR: unresolved rollback state exists; refusing to create another full-KB copy. — verified
that 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 --update
never touches those snapshots, so retrying it (today's only remediation
advice) can never succeed.

  • Adds BRAIN_RECLAIM_STUCK, classifying this refusal distinctly from every
    other held refusal (which keep their existing, correct remediation text
    unchanged — regression-guarded).
  • Gives it accurate remediation: npx ruvnet-brain --uninstall (removes only
    the KB bundle, never the legacy snapshots) then ak sync reinstalls 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 reclaim
    check. installRuvnetBrain() already routes this correctly once kb/ is
    gone (updaterPresent() false → pinned --force --version fresh install) —
    no new install logic, just a test pinning that existing behavior.
  • Reports the legacy-snapshot count/size in the same row via
    legacySnapshotBytes() (bounded, best-effort — only runs inside this rare
    branch).
  • Does not fix the disk bloat. The workaround clears the version block,
    not the legacy snapshots (they're untouched throughout). Filed as
    stuinfla/ruvnet-brain#335,
    registered in the upstream-watch list.
  • Not automated. Stays 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-missing settings.json.statusLine
    key from scratch (only migrates between two known non-empty formats). Hit
    this live when testing --uninstall — it removes the Brain's own
    statusline entry, and both ak status and ak sync --dry-run reported the
    statusline as fine even though it was null on disk. Restored by hand this
    session; 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 in
    touched files)
  • New tests: reclaim-stuck classification, distinct remediation text vs.
    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)
  • Live-verified the underlying workaround this session: uninstalled,
    reinstalled, landed cleanly on 4.3.29, search_ruvnet came back with no
    Claude Code restart, ak sync re-verified clean afterward

Refs stuinfla/ruvnet-brain#335

🤖 Generated with Claude Code

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.
@pacphi
pacphi merged commit be1c1d4 into main Sep 27, 2026
16 checks passed
@pacphi
pacphi deleted the feat/adr-0061-brain-reclaim-remediation branch September 27, 2026 15:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant