garrytan/gstack · error · Error

gbrain sources add ${id} failed: ${add.stderr || add.stdout

Error message

gbrain sources add ${id} failed: ${add.stderr || add.stdout || `exit ${add.status}`}

What it means

Thrown after spawnSync('gbrain', ['sources','add',id,'--path',path,(--federated)]) returns non-zero at the end of drift re-registration (or a fresh registration). Like 321 it surfaces the child's stderr/stdout/exit code. The remove already succeeded, so a failure here leaves the source unregistered.

Source

Thrown at lib/gbrain-sources.ts:252

          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,
      state: { status: "match", registered_path: path },
    };
  }, "gbrain-sources");
}

/**
 * Get page_count for a registered source. Returns null if source is absent or if
 * page_count is missing/invalid in the JSON. Used by the verdict block + preamble
 * variant selection.
 */
export function sourcePageCount(id: string, env?: NodeJS.ProcessEnv): number | null {
  let stdout: string;
  try {
    stdout = execFileSync("gbrain", ["sources", "list", "--json"], {

View on GitHub (pinned to 94993f7401)

Solutions

  1. Run `gbrain sources add <id> --path <path>` manually to see gbrain's own error.
  2. Confirm the path exists and is readable by the gbrain process before syncing.
  3. Ensure no concurrent sync is running (check for a gbrain lock file).
  4. Verify gbrain is on PATH and >= the version that supports your flags.

Example fix

// before
const add = spawnSync('gbrain', addArgs, { encoding:'utf-8', timeout:30_000, env, shell: NEEDS_SHELL_ON_WINDOWS });
if (add.status !== 0) throw new Error(`gbrain sources add ${id} failed: ${add.stderr || add.stdout || `exit ${add.status}`}`);

// after: validate path first, distinguish spawn/signal/exit
if (!fs.existsSync(path)) throw new Error(`cannot add ${id}: path does not exist: ${path}`);
const add = spawnSync('gbrain', addArgs, {...});
if (add.error) throw new Error(`gbrain add ${id} spawn failed: ${add.error.message}`);
if (add.signal) throw new Error(`gbrain add ${id} killed by ${add.signal}`);
if (add.status !== 0) throw new Error(`gbrain sources add ${id} failed: ${add.stderr || add.stdout || `exit ${add.status}`}`);
Defensive patterns

Strategy: validation

Validate before calling

// Validate the path exists and gbrain is callable before triggering an add.
import fs from 'node:fs';
import { spawnSync } from 'node:child_process';

function preflightAdd(id: string, p: string, env = process.env): string | null {
  if (!fs.existsSync(p)) return `path does not exist: ${p}`;
  const v = spawnSync('gbrain', ['--version'], { encoding: 'utf-8', env, shell: process.platform === 'win32' });
  if (v.status !== 0) return 'gbrain not on PATH or not executable';
  return null;
}

const err = preflightAdd(id, path);
if (err) throw new Error(`preflight: ${err}`);

Try / catch

try {
  await ensureSourceRegistered(id, path);
} catch (e) {
  if (/already registered|exists/i.test(String(e?.message ?? ''))) {
    // idempotent — source is registered, treat as success
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: The add spawnSync in ensureSourceRegistered() exits non-zero. Typical causes: path does not exist or is unreadable, the id already exists (race with another sync), --federated rejected by a non-federated gbrain, gbrain not on PATH, or a 30s timeout.

Common situations: Drift sync where the new path is a typo or not yet created; two /sync-gbrain runs racing on the same source; gbrain version that rejects --federated; Windows .cmd shim resolution failure; the path is a symlink whose target is gone.

Related errors


AI-assisted analysis of garrytan/gstack@94993f7401 (2026-08-12). Data as JSON: /api/errors/86790974755e2650. Report an issue: GitHub.