fix(gbrain): pass --confirm-destructive on drift re-register (#1985)

ensureSourceRegistered() handles match-but-different-path by removing the
old source then re-adding it at the new path. The remove was issued as
`gbrain sources remove <id> --yes`, but gbrain >= 0.42 gates `sources
remove` behind `--confirm-destructive` (`--yes` alone no longer suppresses
the data-loss prompt). The remove therefore fails with "To proceed, pass
--confirm-destructive", which ensureSourceRegistered surfaces as "source
registration failed" — aborting the entire /sync-gbrain code stage for any
already-registered source whose path has drifted. The memory and brain-sync
stages still pass, so the code index silently stops refreshing.

The orchestrator's own safeSourcesRemove() already passes
--confirm-destructive; this brings the lib helper in line with that
convention. Keeps --yes for older gbrain.

Tests: extend the fake gbrain shim in gbrain-sources.test.ts to simulate
the gbrain >= 0.42 guard (remove without --confirm-destructive exits 1),
update the drift re-register assertion, and add a regression test that
proves the drift path no longer throws. Both fail on main with the exact
"To proceed, pass --confirm-destructive" error and pass with the fix.

Fixes #1985

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jayesh Betala
2026-07-09 18:58:05 -07:00
committed by Garry Tan
co-authored by Claude Opus 4.8
parent 9adb8ad4ea
commit 0ad9695867
2 changed files with 54 additions and 6 deletions
+9 -2
View File
@@ -124,7 +124,7 @@ export function probeSource(id: string, env?: NodeJS.ProcessEnv): SourceState {
* Behavior:
* - status=absent → `gbrain sources add <id> --path <path> [--federated]`, returns changed=true.
* - status=match + same path → no-op, returns changed=false.
* - status=match + different path → `sources remove` + `sources add`, returns changed=true.
* - status=match + different path → `sources remove --confirm-destructive` + `sources add`, returns changed=true.
* (Skip when reregister_on_drift=false; returns changed=false.)
*
* Caller is responsible for catching errors. The function uses withErrorContext for
@@ -157,8 +157,15 @@ export async function ensureSourceRegistered(
}
// For drift, remove first.
//
// #1985: gbrain >= 0.42 gates `sources remove` behind --confirm-destructive
// (`--yes` alone no longer suppresses the data-loss prompt). Without it the
// remove fails with "To proceed, pass --confirm-destructive", which surfaces
// as "source registration failed" and aborts the whole /sync-gbrain code
// stage for any source that has drifted to a new path. This matches the
// flag the orchestrator's own safeSourcesRemove() already passes.
if (state.status === "drift") {
const rm = spawnSync("gbrain", ["sources", "remove", id, "--yes"], {
const rm = spawnSync("gbrain", ["sources", "remove", id, "--yes", "--confirm-destructive"], {
encoding: "utf-8",
timeout: 30_000,
env,