mirror of https://github.com/garrytan/gstack.git
fix(brain-context-load): probe gbrain via execFile, not shell builtin (#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 #1560. Closes #1559.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
59a9b841af
commit
8b03c357ef
|
|
@ -192,7 +192,10 @@ function resolveSkillFile(args: CliArgs): string | null {
|
|||
|
||||
function gbrainAvailable(): boolean {
|
||||
try {
|
||||
execFileSync("command", ["-v", "gbrain"], { stdio: "ignore" });
|
||||
execFileSync("gbrain", ["--version"], {
|
||||
stdio: "ignore",
|
||||
timeout: MCP_TIMEOUT_MS,
|
||||
});
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@
|
|||
*/
|
||||
|
||||
import { describe, it, expect } from "bun:test";
|
||||
import { mkdtempSync, writeFileSync, mkdirSync, rmSync } from "fs";
|
||||
import { chmodSync, mkdtempSync, writeFileSync, mkdirSync, rmSync } from "fs";
|
||||
import { tmpdir } from "os";
|
||||
import { join } from "path";
|
||||
import { delimiter, join } from "path";
|
||||
import { spawnSync } from "child_process";
|
||||
|
||||
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:
|
|||
};
|
||||
}
|
||||
|
||||
function writeFakeGbrain(binDir: string): void {
|
||||
if (process.platform === "win32") {
|
||||
writeFileSync(
|
||||
join(binDir, "gbrain.cmd"),
|
||||
"@echo off\r\nif \"%1\"==\"--version\" (\r\n echo gbrain 0.test\r\n) else (\r\n echo fake gbrain %*\r\n)\r\n",
|
||||
"utf-8",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const fakeBin = join(binDir, "gbrain");
|
||||
writeFileSync(
|
||||
fakeBin,
|
||||
`#!/bin/sh
|
||||
if [ "$1" = "--version" ]; then
|
||||
echo "gbrain 0.test"
|
||||
else
|
||||
echo "fake gbrain $*"
|
||||
fi
|
||||
`,
|
||||
"utf-8",
|
||||
);
|
||||
chmodSync(fakeBin, 0o755);
|
||||
}
|
||||
|
||||
function prependPath(binDir: string): Record<string, string> {
|
||||
const pathKey = Object.keys(process.env).find((key) => key.toLowerCase() === "path") || "PATH";
|
||||
const currentPath = process.env[pathKey] || "";
|
||||
return { [pathKey]: `${binDir}${delimiter}${currentPath}` };
|
||||
}
|
||||
|
||||
describe("gstack-brain-context-load CLI", () => {
|
||||
it("--help exits 0 with usage", () => {
|
||||
const r = runScript(["--help"]);
|
||||
|
|
@ -204,6 +235,23 @@ gbrain:
|
|||
});
|
||||
|
||||
describe("gstack-brain-context-load — graceful gbrain absence", () => {
|
||||
it("uses gbrain when a binary is available on PATH", () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "gstack-bcl-"));
|
||||
const binDir = join(dir, "bin");
|
||||
mkdirSync(binDir);
|
||||
writeFakeGbrain(binDir);
|
||||
|
||||
try {
|
||||
const r = runScript(["--repo", "test-repo", "--explain"], prependPath(binDir));
|
||||
expect(r.exitCode).toBe(0);
|
||||
expect(r.stderr).toContain("OK");
|
||||
expect(r.stderr).not.toContain("gbrain CLI missing");
|
||||
expect(r.stdout).toContain("fake gbrain list_pages");
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("vector + list queries still complete (with SKIP) when gbrain CLI is missing", () => {
|
||||
// We can't easily un-install gbrain; rely on the helper's own missing-binary
|
||||
// detection. The default manifest uses kind: list which calls gbrain. If
|
||||
|
|
|
|||
Loading…
Reference in New Issue