garrytan/gstack · error · Error
gbrain sources list returned non-JSON output: ${(err as Erro
Error message
gbrain sources list returned non-JSON output: ${(err as Error).message} What it means
Thrown by probeSource() when gbrain sources list --json exits successfully and prints to stdout, but JSON.parse(stdout) throws. It captures the parse error message so the developer can see what tripped the parser. This is distinct from 316 (no binary), 317 (DB/config), and 315 (no JSON emitted at all) — here stdout exists but is not valid JSON.
Source
Thrown at lib/gbrain-sources.ts:143
shell: NEEDS_SHELL_ON_WINDOWS, // #1731: gbrain is a .cmd shim on Windows
});
} catch (err) {
const e = err as NodeJS.ErrnoException & { stderr?: Buffer };
const stderr = e.stderr?.toString() || "";
if (e.code === "ENOENT" || stderr.includes("command not found")) {
throw new Error("gbrain CLI not on PATH");
}
if (stderr.includes("Cannot connect to database") || stderr.includes("config.json")) {
throw new Error("gbrain not configured (run /setup-gbrain)");
}
throw err;
}
let parsed: unknown;
try {
parsed = JSON.parse(stdout);
} catch (err) {
throw new Error(`gbrain sources list returned non-JSON output: ${(err as Error).message}`);
}
const sources = parseSourcesList(parsed);
const match = sources.find((s) => s.id === id);
if (!match) return { status: "absent" };
return {
status: "match",
registered_path: match.local_path,
};
}
/**
* Ensure source <id> is registered at <path>. Idempotent.
*
* 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 --confirm-destructive` + `sources add`, returns changed=true.View on GitHub (pinned to 94993f7401)
Solutions
- Run `gbrain sources list --json` manually and inspect the raw stdout to see what preceded the JSON.
- Upgrade gbrain to a version that emits clean JSON on stdout with logs on stderr.
- If a log line is the culprit, configure gbrain's log level to suppress stdout logging.
- Pipe through a JSON extractor if a workaround is needed before upgrading.
Defensive patterns
Strategy: try-catch
Validate before calling
import { execFileSync } from 'child_process';
function gbrainStdoutIsJson(env: NodeJS.ProcessEnv = process.env): boolean {
try {
const out = execFileSync('gbrain', ['sources', 'list', '--json'], { encoding: 'utf-8', env, timeout: 5_000 });
JSON.parse(out); return true;
} catch { return false; }
} Try / catch
try {
return probeSource(id, env);
} catch (e) {
if (e instanceof Error && e.message.startsWith('gbrain sources list returned non-JSON')) {
// Upgrade gbrain or redirect its logs off stdout, then retry
throw new Error('gbrain emitted non-JSON stdout. Upgrade gbrain and retry.');
}
throw e;
} Prevention
- Pin a gbrain version that emits clean JSON on stdout.
- Configure gbrain to send logs to stderr, not stdout.
- Smoke-test `gbrain sources list --json | jq .` after install.
- Upgrade gbrain promptly when --json semantics change.
When it happens
Trigger: gbrain sources list --json exits 0 with stdout containing non-JSON text (e.g. a log line before the JSON, a warning, an incomplete buffer, or a human-format table because --json was ignored). JSON.parse throws and the catch wraps it.
Common situations: gbrain version that prints a deprecation/log line to stdout before the JSON; --json flag ignored in an older version so human text is emitted; stdout truncated by a buffer limit or pipe closure; a plugin injected non-JSON output.
Related errors
- gbrain sources list returned no JSON
- load-html: --from-file JSON parse failed: ${e.message}
- gbrain CLI not on PATH
- pdf: --from-file requires a path
- Unknown pdf flag: ${a}
AI-assisted analysis of garrytan/gstack@94993f7401 (2026-08-12).
Data as JSON: /api/errors/57c7dbe50b271518.
Report an issue: GitHub.