Skip to content

Commit 79d5189

Browse files
garrytanclaude
authored andcommitted
fix(brain-context-load): probe gbrain via execFile, not shell builtin (garrytan#1559)
gbrainAvailable() used `execFileSync("command", ["-v", "gbrain"])`, which fails in any environment where the `command` builtin isn't on the spawned process's PATH (most non-interactive shells). The probe then reported gbrain as missing even when it was installed, and context-load silently skipped vector/list queries. Fix: probe `gbrain --version` directly with a 500ms timeout (matching the rest of the file's MCP_TIMEOUT_MS). Same semantics, works everywhere execFile works. Contributed by @jbetala7 via garrytan#1560. Closes garrytan#1559. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 3e906c1 commit 79d5189

2 files changed

Lines changed: 54 additions & 3 deletions

File tree

bin/gstack-brain-context-load.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,10 @@ function resolveSkillFile(args: CliArgs): string | null {
192192

193193
function gbrainAvailable(): boolean {
194194
try {
195-
execFileSync("command", ["-v", "gbrain"], { stdio: "ignore" });
195+
execFileSync("gbrain", ["--version"], {
196+
stdio: "ignore",
197+
timeout: MCP_TIMEOUT_MS,
198+
});
196199
return true;
197200
} catch {
198201
return false;

test/gstack-brain-context-load.test.ts

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
*/
88

99
import { describe, it, expect } from "bun:test";
10-
import { mkdtempSync, writeFileSync, mkdirSync, rmSync } from "fs";
10+
import { chmodSync, mkdtempSync, writeFileSync, mkdirSync, rmSync } from "fs";
1111
import { tmpdir } from "os";
12-
import { join } from "path";
12+
import { delimiter, join } from "path";
1313
import { spawnSync } from "child_process";
1414

1515
const SCRIPT = join(import.meta.dir, "..", "bin", "gstack-brain-context-load.ts");
@@ -27,6 +27,37 @@ function runScript(args: string[], env: Record<string, string> = {}): { stdout:
2727
};
2828
}
2929

30+
function writeFakeGbrain(binDir: string): void {
31+
if (process.platform === "win32") {
32+
writeFileSync(
33+
join(binDir, "gbrain.cmd"),
34+
"@echo off\r\nif \"%1\"==\"--version\" (\r\n echo gbrain 0.test\r\n) else (\r\n echo fake gbrain %*\r\n)\r\n",
35+
"utf-8",
36+
);
37+
return;
38+
}
39+
40+
const fakeBin = join(binDir, "gbrain");
41+
writeFileSync(
42+
fakeBin,
43+
`#!/bin/sh
44+
if [ "$1" = "--version" ]; then
45+
echo "gbrain 0.test"
46+
else
47+
echo "fake gbrain $*"
48+
fi
49+
`,
50+
"utf-8",
51+
);
52+
chmodSync(fakeBin, 0o755);
53+
}
54+
55+
function prependPath(binDir: string): Record<string, string> {
56+
const pathKey = Object.keys(process.env).find((key) => key.toLowerCase() === "path") || "PATH";
57+
const currentPath = process.env[pathKey] || "";
58+
return { [pathKey]: `${binDir}${delimiter}${currentPath}` };
59+
}
60+
3061
describe("gstack-brain-context-load CLI", () => {
3162
it("--help exits 0 with usage", () => {
3263
const r = runScript(["--help"]);
@@ -204,6 +235,23 @@ gbrain:
204235
});
205236

206237
describe("gstack-brain-context-load — graceful gbrain absence", () => {
238+
it("uses gbrain when a binary is available on PATH", () => {
239+
const dir = mkdtempSync(join(tmpdir(), "gstack-bcl-"));
240+
const binDir = join(dir, "bin");
241+
mkdirSync(binDir);
242+
writeFakeGbrain(binDir);
243+
244+
try {
245+
const r = runScript(["--repo", "test-repo", "--explain"], prependPath(binDir));
246+
expect(r.exitCode).toBe(0);
247+
expect(r.stderr).toContain("OK");
248+
expect(r.stderr).not.toContain("gbrain CLI missing");
249+
expect(r.stdout).toContain("fake gbrain list_pages");
250+
} finally {
251+
rmSync(dir, { recursive: true, force: true });
252+
}
253+
});
254+
207255
it("vector + list queries still complete (with SKIP) when gbrain CLI is missing", () => {
208256
// We can't easily un-install gbrain; rely on the helper's own missing-binary
209257
// detection. The default manifest uses kind: list which calls gbrain. If

0 commit comments

Comments
 (0)