{"record":{"id":"0c7b59e8b9053910","repo":"garrytan/gstack","slug":"gbrain-sources-remove-id-failed-rm-stderr","errorCode":null,"errorMessage":"gbrain sources remove ${id} failed: ${rm.stderr || rm.stdout || `exit ${rm.status}`}","messagePattern":"gbrain sources remove (.+?) failed: (.+?)`\\}","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"lib/gbrain-sources.ts","lineNumber":238,"sourceCode":"        );\n      }\n      const decision = decideSourceRemove(id, env ?? process.env, options.removeDecision ?? {});\n      if (!decision.allow) {\n        throw new Error(`refusing drift re-register of ${id}: ${decision.reason}`);\n      }\n\n      const rm = spawnSync(\n        \"gbrain\",\n        [\"sources\", \"remove\", id, \"--yes\", \"--confirm-destructive\", ...decision.extraArgs],\n        {\n          encoding: \"utf-8\",\n          timeout: 30_000,\n          env,\n          shell: NEEDS_SHELL_ON_WINDOWS, // #1731: gbrain is a .cmd shim on Windows\n        },\n      );\n      if (rm.status !== 0) {\n        throw new Error(`gbrain sources remove ${id} failed: ${rm.stderr || rm.stdout || `exit ${rm.status}`}`);\n      }\n    }\n\n    // Add.\n    const addArgs = [\"sources\", \"add\", id, \"--path\", path];\n    if (federated) addArgs.push(\"--federated\");\n    const add = spawnSync(\"gbrain\", addArgs, {\n      encoding: \"utf-8\",\n      timeout: 30_000,\n      env,\n      shell: NEEDS_SHELL_ON_WINDOWS, // #1731: gbrain is a .cmd shim on Windows\n    });\n    if (add.status !== 0) {\n      throw new Error(`gbrain sources add ${id} failed: ${add.stderr || add.stdout || `exit ${add.status}`}`);\n    }\n\n    return {\n      changed: true,","sourceCodeStart":220,"sourceCodeEnd":256,"githubUrl":"https://github.com/garrytan/gstack/blob/94993f74012782fd94416dd44b8314f6363a13a4/lib/gbrain-sources.ts#L220-L256","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)."],"exampleFix":"// before\nconst rm = spawnSync('gbrain', ['sources','remove',id,'--yes','--confirm-destructive', ...decision.extraArgs], { encoding:'utf-8', timeout:30_000, env, shell: NEEDS_SHELL_ON_WINDOWS });\nif (rm.status !== 0) throw new Error(`gbrain sources remove ${id} failed: ${rm.stderr || rm.stdout || `exit ${rm.status}`}`);\n\n// after: surface signal/timeout too, and pre-flight the id existence\nif (probe.status === 'absent') { /* nothing to remove */ }\nelse {\n  const rm = spawnSync('gbrain', [...], {...});\n  if (rm.error) throw new Error(`gbrain remove ${id} spawn failed: ${rm.error.message}`);\n  if (rm.signal) throw new Error(`gbrain remove ${id} killed by ${rm.signal}`);\n  if (rm.status !== 0) throw new Error(`gbrain sources remove ${id} failed: ${rm.stderr || rm.stdout || `exit ${rm.status}`}`);\n}","handlingStrategy":"try-catch","validationCode":"// Pre-flight: is gbrain present and is the source registered before attempting a drift remove?\nimport { spawnSync } from 'node:child_process';\n\nfunction gbrainWorks(env = process.env): boolean {\n  const r = spawnSync('gbrain', ['--version'], { encoding: 'utf-8', env, shell: process.platform === 'win32' });\n  return r.status === 0;\n}\n\nfunction sourceExists(id: string, env = process.env): boolean {\n  const r = spawnSync('gbrain', ['sources', 'show', id, '--json'], { encoding: 'utf-8', env, shell: process.platform === 'win32' });\n  return r.status === 0;\n}","typeGuard":null,"tryCatchPattern":"try {\n  await ensureSourceRegistered(id, path, { reregister_on_drift: true });\n} catch (e) {\n  const msg = String(e?.message ?? e);\n  if (msg.startsWith('gbrain sources remove ') && /not registered|not found/i.test(msg)) {\n    // source already gone — fall through to a plain add\n    await ensureSourceRegistered(id, path, { reregister_on_drift: false });\n  } else {\n    throw e;\n  }\n}","preventionTips":["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."],"tags":["gbrain","subprocess","spawnsync","destructive-op","windows"],"backgroundTag":null,"analyzedSha":"94993f74012782fd94416dd44b8314f6363a13a4","analyzedAt":"2026-08-12T04:06:23.140Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}