From 03968f0a7c3f54d4181a20c37ee77594de26974f Mon Sep 17 00:00:00 2001 From: garrytan Date: Fri, 16 Oct 2026 22:15:32 +0000 Subject: [PATCH] fix(pregate): spawn the supervised shell through lib/script-invocation (#3197 tripwire) The supervisor now resolves bash via bashCommandInvocation, so Windows uses Git Bash instead of a PATH bash, and an unresolvable bash reports spawn-failed. guards.ts and lanes.ts no longer spawn bash (they run through supervise.ts), so their KNOWN_SITES entries are removed. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --- lib/pregate/supervise.ts | 9 ++++++++- test/script-invocation-tripwire.test.ts | 2 -- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/pregate/supervise.ts b/lib/pregate/supervise.ts index 1ba287bd7..0d6aac3a0 100644 --- a/lib/pregate/supervise.ts +++ b/lib/pregate/supervise.ts @@ -20,6 +20,7 @@ */ import { spawn, spawnSync } from 'node:child_process'; import * as fs from 'node:fs'; +import { bashCommandInvocation } from '../script-invocation'; export type ProgressAdapter = 'bun'; export const PROGRESS_ADAPTERS: Record = { bun: /(?:^|[\s;&|(])bun\s+test\b/ }; @@ -134,9 +135,15 @@ export function supervise(o: SuperviseOptions): Promise { let fd: number; try { fd = fs.openSync(o.log, 'a'); } catch (e: any) { resolve({ outcome: 'spawn-failed', exit: null, signal: null, start, end: start, lastFile: null, error: e.message, ms: 0 }); return; } + const inv = bashCommandInvocation(`set -o pipefail; ${o.command}`); + if (!inv.ok) { + fs.closeSync(fd); + resolve({ outcome: 'spawn-failed', exit: null, signal: null, start, end: start, lastFile: null, error: inv.error, ms: Date.now() - started }); + return; + } let child: ReturnType; try { - child = spawn('bash', ['-c', `set -o pipefail; ${o.command}`], { cwd: o.cwd, env: o.env, stdio: ['ignore', fd, fd], detached: process.platform !== 'win32', windowsHide: true }); + child = spawn(inv.cmd, inv.argv, { cwd: o.cwd, env: o.env, stdio: ['ignore', fd, fd], detached: process.platform !== 'win32', windowsHide: true }); } catch (e: any) { fs.closeSync(fd); resolve({ outcome: 'spawn-failed', exit: null, signal: null, start, end: start, lastFile: null, error: e.message, ms: Date.now() - started }); diff --git a/test/script-invocation-tripwire.test.ts b/test/script-invocation-tripwire.test.ts index 79d9d9eeb..c0596d6c8 100644 --- a/test/script-invocation-tripwire.test.ts +++ b/test/script-invocation-tripwire.test.ts @@ -23,8 +23,6 @@ const SPAWN_BARE_BASH = /\b(?:spawn|spawnSync|execFile|execFileSync|execSync)\(\ const COMMENT = /^\s*(?:\/\/|\*|\/\*)/; const KNOWN_SITES: Record = { - 'lib/pregate/checks/guards.ts': 'PR 5 (a #3190 file): repo guard commands move to runBashSync', - 'lib/pregate/checks/lanes.ts': 'PR 5 (a #3190 file): the lane runner moves to runBashSync', 'lib/evidence-bundle.ts': 'outside this wave: bundle replay commands (bash -lc) move to runBashSync', 'bin/gstack-brain-context-load.ts': 'outside this wave: the brain context loader moves to runBashSync', 'bin/gstack-issue-links': 'outside this wave: the gstack-issue-guard spawn moves to runScriptSync',