garrytan/gstack · error · Error
gbrain sources remove ${id} failed: ${rm.stderr || rm.stdout
Error message
gbrain sources remove ${id} failed: ${rm.stderr || rm.stdout || `exit ${rm.status}`} What it means
Thrown after spawnSync('gbrain', ['sources','remove',id,'--yes','--confirm-destructive', ...]) returns a non-zero status during drift re-registration. The message embeds the child's stderr, then stdout, then the raw exit code so the underlying gbrain failure is visible. This is a wrapper around a failed subprocess, not a logic error in this library.
Source
Thrown at lib/gbrain-sources.ts:238
);
}
const decision = decideSourceRemove(id, env ?? process.env, options.removeDecision ?? {});
if (!decision.allow) {
throw new Error(`refusing drift re-register of ${id}: ${decision.reason}`);
}
const rm = spawnSync(
"gbrain",
["sources", "remove", id, "--yes", "--confirm-destructive", ...decision.extraArgs],
{
encoding: "utf-8",
timeout: 30_000,
env,
shell: NEEDS_SHELL_ON_WINDOWS, // #1731: gbrain is a .cmd shim on Windows
},
);
if (rm.status !== 0) {
throw new Error(`gbrain sources remove ${id} failed: ${rm.stderr || rm.stdout || `exit ${rm.status}`}`);
}
}
// Add.
const addArgs = ["sources", "add", id, "--path", path];
if (federated) addArgs.push("--federated");
const add = spawnSync("gbrain", addArgs, {
encoding: "utf-8",
timeout: 30_000,
env,
shell: NEEDS_SHELL_ON_WINDOWS, // #1731: gbrain is a .cmd shim on Windows
});
if (add.status !== 0) {
throw new Error(`gbrain sources add ${id} failed: ${add.stderr || add.stdout || `exit ${add.status}`}`);
}
return {
changed: true,View on GitHub (pinned to 94993f7401)
Solutions
- Run `gbrain sources remove <id> --yes --confirm-destructive` manually and read the real error from gbrain directly.
- Verify gbrain is installed and on PATH: `gbrain --version` (on Windows ensure the .cmd shim resolves).
- Upgrade gbrain to >= 0.42 so --confirm-destructive is recognized.
- If the source is already gone, re-probe registration (the next sync should see status 'absent' and skip straight to add).
Example fix
// before
const rm = spawnSync('gbrain', ['sources','remove',id,'--yes','--confirm-destructive', ...decision.extraArgs], { encoding:'utf-8', timeout:30_000, env, shell: NEEDS_SHELL_ON_WINDOWS });
if (rm.status !== 0) throw new Error(`gbrain sources remove ${id} failed: ${rm.stderr || rm.stdout || `exit ${rm.status}`}`);
// after: surface signal/timeout too, and pre-flight the id existence
if (probe.status === 'absent') { /* nothing to remove */ }
else {
const rm = spawnSync('gbrain', [...], {...});
if (rm.error) throw new Error(`gbrain remove ${id} spawn failed: ${rm.error.message}`);
if (rm.signal) throw new Error(`gbrain remove ${id} killed by ${rm.signal}`);
if (rm.status !== 0) throw new Error(`gbrain sources remove ${id} failed: ${rm.stderr || rm.stdout || `exit ${rm.status}`}`);
} Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-flight: is gbrain present and is the source registered before attempting a drift remove?
import { spawnSync } from 'node:child_process';
function gbrainWorks(env = process.env): boolean {
const r = spawnSync('gbrain', ['--version'], { encoding: 'utf-8', env, shell: process.platform === 'win32' });
return r.status === 0;
}
function sourceExists(id: string, env = process.env): boolean {
const r = spawnSync('gbrain', ['sources', 'show', id, '--json'], { encoding: 'utf-8', env, shell: process.platform === 'win32' });
return r.status === 0;
} Try / catch
try {
await ensureSourceRegistered(id, path, { reregister_on_drift: true });
} catch (e) {
const msg = String(e?.message ?? e);
if (msg.startsWith('gbrain sources remove ') && /not registered|not found/i.test(msg)) {
// source already gone — fall through to a plain add
await ensureSourceRegistered(id, path, { reregister_on_drift: false });
} else {
throw e;
}
} Prevention
- Ensure gbrain is installed and on PATH before running /sync-gbrain (`gbrain --version`).
- On Windows, confirm the .cmd shim resolves (NEEDS_SHELL_ON_WINDOWS path).
- Keep gbrain >= 0.42 so --confirm-destructive is accepted.
- Avoid concurrent syncs that race on the same source id.
When it happens
Trigger: The drift branch of ensureSourceRegistered() runs the remove and rm.status !== 0. Causes include: gbrain not on PATH (especially on Windows where it is a .cmd shim requiring shell:true), the source id already removed by a concurrent process, gbrain < 0.42 rejecting the flags, a corrupt index, or a timeout-style exit.
Common situations: gbrain binary missing or not on PATH in a fresh CI container; Windows where NEEDS_SHELL_ON_WINDOWS mishandles the .cmd shim; a previous partial sync left the source half-removed; gbrain version skew where --confirm-destructive is unknown (#1985 regression); a 30s timeout hit on a slow disk.
Related errors
- gbrain sources add ${id} failed: ${add.stderr || add.stdout
- refusing drift re-register of ${id}: ${decision.reason}
- git ${args.join(' ')} failed (exit ${result.status}): ${stde
- Unsupported file URL host: ${segment}. Use file:///<absolute
- gbrain sources list returned no JSON
AI-assisted analysis of garrytan/gstack@94993f7401 (2026-08-12).
Data as JSON: /api/errors/0c7b59e8b9053910.
Report an issue: GitHub.