diff --git a/harness/pstack/claude-code/skills/poteto-mode/scripts/watch-pr/github.test.ts b/harness/pstack/claude-code/skills/poteto-mode/scripts/watch-pr/github.test.ts index 4e5893838..98a2b6a7c 100644 --- a/harness/pstack/claude-code/skills/poteto-mode/scripts/watch-pr/github.test.ts +++ b/harness/pstack/claude-code/skills/poteto-mode/scripts/watch-pr/github.test.ts @@ -495,4 +495,25 @@ printf '%s\\n' '[{"number":42,"headRefName":"feature","baseRefName":"main","isCr } }); + it("reports a gh that cannot start as a query failure", () => { + const directory = mkdtempSync(join(tmpdir(), "watch-pr-no-gh-")); + try { + const program = `import { GhGitHubReader, WatcherQueryError } from ${JSON.stringify(import.meta.dir + "/github.ts")}; + try { + await new GhGitHubReader().pullRequest({ owner: "owner", repo: "repo", number: 42 }); + } catch (error) { + console.log(JSON.stringify(error instanceof WatcherQueryError ? error.failure.kind : String(error))); + }`; + const child = spawnSync(process.execPath, ["-e", program], { + encoding: "utf8", timeout: 2000, + env: { ...process.env, PATH: directory }, + }); + expect(child.error).toBeUndefined(); + expect(child.status).toBe(0); + expect(JSON.parse(child.stdout)).toBe("command-exit"); + } finally { + rmSync(directory, { recursive: true, force: true }); + } + }); + }); diff --git a/harness/pstack/claude-code/skills/poteto-mode/scripts/watch-pr/github.ts b/harness/pstack/claude-code/skills/poteto-mode/scripts/watch-pr/github.ts index 123a33b19..c6493dfd5 100644 --- a/harness/pstack/claude-code/skills/poteto-mode/scripts/watch-pr/github.ts +++ b/harness/pstack/claude-code/skills/poteto-mode/scripts/watch-pr/github.ts @@ -30,10 +30,20 @@ export class ChecksUnavailable extends WatcherQueryError { const firstLine = (value: string): string => value.trim().split(/\r?\n/, 1)[0]?.slice(0, 240) ?? ""; function run(argv: readonly [string, ...string[]]): Promise { - return new Promise((resolve, reject) => { - const child = spawn(argv[0], argv.slice(1), { - stdio: ["ignore", "pipe", "pipe"], - }); + return new Promise((resolve) => { + // A command that cannot start fails like one that exits nonzero; Bun + // throws here while Node emits "error". + const failed = (error: unknown) => + resolve({ code: -1, stdout: "", stderr: String(error) }); + let child; + try { + child = spawn(argv[0], argv.slice(1), { + stdio: ["ignore", "pipe", "pipe"], + }); + } catch (error) { + failed(error); + return; + } let stdout = ""; let stderr = ""; child.stdout.setEncoding("utf8"); @@ -44,7 +54,7 @@ function run(argv: readonly [string, ...string[]]): Promise { child.stderr.on("data", (chunk: string) => { stderr += chunk; }); - child.on("error", reject); + child.on("error", failed); child.on("close", (code) => resolve({ code: code ?? -1, stdout, stderr })); }); } diff --git a/pstack/skills/poteto-mode/scripts/watch-pr/github.test.ts b/pstack/skills/poteto-mode/scripts/watch-pr/github.test.ts index 4e5893838..98a2b6a7c 100644 --- a/pstack/skills/poteto-mode/scripts/watch-pr/github.test.ts +++ b/pstack/skills/poteto-mode/scripts/watch-pr/github.test.ts @@ -495,4 +495,25 @@ printf '%s\\n' '[{"number":42,"headRefName":"feature","baseRefName":"main","isCr } }); + it("reports a gh that cannot start as a query failure", () => { + const directory = mkdtempSync(join(tmpdir(), "watch-pr-no-gh-")); + try { + const program = `import { GhGitHubReader, WatcherQueryError } from ${JSON.stringify(import.meta.dir + "/github.ts")}; + try { + await new GhGitHubReader().pullRequest({ owner: "owner", repo: "repo", number: 42 }); + } catch (error) { + console.log(JSON.stringify(error instanceof WatcherQueryError ? error.failure.kind : String(error))); + }`; + const child = spawnSync(process.execPath, ["-e", program], { + encoding: "utf8", timeout: 2000, + env: { ...process.env, PATH: directory }, + }); + expect(child.error).toBeUndefined(); + expect(child.status).toBe(0); + expect(JSON.parse(child.stdout)).toBe("command-exit"); + } finally { + rmSync(directory, { recursive: true, force: true }); + } + }); + }); diff --git a/pstack/skills/poteto-mode/scripts/watch-pr/github.ts b/pstack/skills/poteto-mode/scripts/watch-pr/github.ts index 123a33b19..c6493dfd5 100644 --- a/pstack/skills/poteto-mode/scripts/watch-pr/github.ts +++ b/pstack/skills/poteto-mode/scripts/watch-pr/github.ts @@ -30,10 +30,20 @@ export class ChecksUnavailable extends WatcherQueryError { const firstLine = (value: string): string => value.trim().split(/\r?\n/, 1)[0]?.slice(0, 240) ?? ""; function run(argv: readonly [string, ...string[]]): Promise { - return new Promise((resolve, reject) => { - const child = spawn(argv[0], argv.slice(1), { - stdio: ["ignore", "pipe", "pipe"], - }); + return new Promise((resolve) => { + // A command that cannot start fails like one that exits nonzero; Bun + // throws here while Node emits "error". + const failed = (error: unknown) => + resolve({ code: -1, stdout: "", stderr: String(error) }); + let child; + try { + child = spawn(argv[0], argv.slice(1), { + stdio: ["ignore", "pipe", "pipe"], + }); + } catch (error) { + failed(error); + return; + } let stdout = ""; let stderr = ""; child.stdout.setEncoding("utf8"); @@ -44,7 +54,7 @@ function run(argv: readonly [string, ...string[]]): Promise { child.stderr.on("data", (chunk: string) => { stderr += chunk; }); - child.on("error", reject); + child.on("error", failed); child.on("close", (code) => resolve({ code: code ?? -1, stdout, stderr })); }); }