fix: sweep — every sync spawn in the test trees carries a timeout (436 sites, 157 files)

spawnSync/execSync/Bun.spawnSync BLOCK the main thread, so bun's in-process
per-test timeout can never fire while one waits — a hung child (stdin read,
network probe, dead daemon) wedges the whole shard until the runner's
external wall-clock SIGKILL. This exact class reached main: free-tests run
33262077256, test/gstack-memory-ingest.test.ts (normally 2.3s) held shard 2
at the 360s wall while its five siblings finished in ~65s.

Mechanical sweep in two waves (12 + 4 fan-out agents, every edit verified
against its call site): default timeout: 30_000 (matches the free runner's
per-test budget), 120_000 for genuinely slow ops (installs, builds,
playwright, provider CLIs), helper wrappers fixed ONCE where call sites
route through them. Sites that only LOOK like calls (string fixtures, grep
needles, comments) were skipped with reasons — the enforcement commit that
follows marks them exempt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-08-31 04:49:57 +00:00
co-authored by Claude Fable 5
parent 79efe395dc
commit 3e674e4c01
139 changed files with 451 additions and 331 deletions
+1 -1
View File
@@ -13,7 +13,7 @@ describe('build: server-node.mjs', () => {
// Skip rather than fail so plain `bun test` without a prior build passes.
return;
}
expect(() => execSync(`node --check ${SERVER_NODE}`, { stdio: 'pipe' })).not.toThrow();
expect(() => execSync(`node --check ${SERVER_NODE}`, { stdio: 'pipe', timeout: 30_000 })).not.toThrow();
});
test('does not inline @ngrok/ngrok (must be external)', () => {
+14 -14
View File
@@ -27,7 +27,7 @@ describe('bun-polyfill', () => {
const elapsed = Date.now() - start;
console.log(elapsed >= 40 ? 'OK' : 'TOO_FAST');
})();
`], { stdout: 'pipe', stderr: 'pipe' });
`], { stdout: 'pipe', stderr: 'pipe', timeout: 30_000 });
expect(result.stdout.toString().trim()).toBe('OK');
expect(result.exitCode).toBe(0);
});
@@ -38,7 +38,7 @@ describe('bun-polyfill', () => {
const r = Bun.spawnSync(['echo', 'hello'], { stdout: 'pipe' });
console.log(r.stdout.toString().trim());
console.log('exit:' + r.exitCode);
`], { stdout: 'pipe', stderr: 'pipe' });
`], { stdout: 'pipe', stderr: 'pipe', timeout: 30_000 });
const lines = result.stdout.toString().trim().split('\n');
expect(lines[0]).toBe('hello');
expect(lines[1]).toBe('exit:0');
@@ -51,7 +51,7 @@ describe('bun-polyfill', () => {
console.log(typeof p.pid === 'number' ? 'HAS_PID' : 'NO_PID');
console.log(typeof p.kill === 'function' ? 'HAS_KILL' : 'NO_KILL');
console.log(typeof p.unref === 'function' ? 'HAS_UNREF' : 'NO_UNREF');
`], { stdout: 'pipe', stderr: 'pipe' });
`], { stdout: 'pipe', stderr: 'pipe', timeout: 30_000 });
const lines = result.stdout.toString().trim().split('\n');
expect(lines[0]).toBe('HAS_PID');
expect(lines[1]).toBe('HAS_KILL');
@@ -70,7 +70,7 @@ describe('bun-polyfill', () => {
console.log(typeof p.exited === 'object' && typeof p.exited.then === 'function' ? 'IS_PROMISE' : 'NOT_PROMISE');
console.log('exit:' + await p.exited);
})();
`], { stdout: 'pipe', stderr: 'pipe' });
`], { stdout: 'pipe', stderr: 'pipe', timeout: 30_000 });
const lines = result.stdout.toString().trim().split('\n');
expect(lines[0]).toBe('IS_PROMISE');
expect(lines[1]).toBe('exit:0');
@@ -83,7 +83,7 @@ describe('bun-polyfill', () => {
const p = Bun.spawn(['node', '-e', 'process.exit(3)'], { stdio: ['ignore', 'ignore', 'ignore'] });
console.log('exit:' + await p.exited);
})();
`], { stdout: 'pipe', stderr: 'pipe' });
`], { stdout: 'pipe', stderr: 'pipe', timeout: 30_000 });
expect(result.stdout.toString().trim()).toBe('exit:3');
});
@@ -100,7 +100,7 @@ describe('bun-polyfill', () => {
const out = await new Response(p.stdout).text();
console.log(out + ':' + code);
})();
`], { stdout: 'pipe', stderr: 'pipe' });
`], { stdout: 'pipe', stderr: 'pipe', timeout: 30_000 });
expect(result.stdout.toString().trim()).toBe('ready:0');
});
@@ -120,7 +120,7 @@ describe('bun-polyfill', () => {
]).catch(() => 'TIMEOUT');
console.log('exit:' + code);
})();
`], { stdout: 'pipe', stderr: 'pipe' });
`], { stdout: 'pipe', stderr: 'pipe', timeout: 30_000 });
// Anything other than 'TIMEOUT' (and ideally a non-zero number) means the
// lifecycle promise resolved on the spawn error.
const out = result.stdout.toString().trim();
@@ -139,7 +139,7 @@ describe('bun-polyfill', () => {
setTimeout(() => p.kill('SIGTERM'), 150);
console.log('exit:' + await p.exited);
})();
`], { stdout: 'pipe', stderr: 'pipe' });
`], { stdout: 'pipe', stderr: 'pipe', timeout: 30_000 });
// SIGTERM = 15 → 128 + 15 = 143.
expect(result.stdout.toString().trim()).toBe('exit:143');
});
@@ -166,7 +166,7 @@ describe('bun-polyfill', () => {
const out = await new Response(p.stdout).text();
console.log(out.length + ':' + code);
})();
`], { stdout: 'pipe', stderr: 'pipe' });
`], { stdout: 'pipe', stderr: 'pipe', timeout: 30_000 });
expect(result.stdout.toString().trim()).toBe('1024:0');
});
@@ -197,7 +197,7 @@ describe('bun-polyfill', () => {
const out = await new Response(p.stdout).text();
console.log(out.length + ':' + code);
})().catch((e) => { console.log('THREW:' + e.message); });
`], { stdout: 'pipe', stderr: 'pipe' });
`], { stdout: 'pipe', stderr: 'pipe', timeout: 30_000 });
expect(result.stdout.toString().trim()).toBe('1048576:0');
}, 15000);
@@ -217,7 +217,7 @@ describe('bun-polyfill', () => {
console.log(typeof server.stop === 'function' ? 'HAS_STOP' : 'NO_STOP');
console.log(typeof server.port === 'number' ? 'HAS_PORT' : 'NO_PORT');
server.stop();
`], { stdout: 'pipe', stderr: 'pipe' });
`], { stdout: 'pipe', stderr: 'pipe', timeout: 30_000 });
const lines = result.stdout.toString().trim().split('\n');
expect(lines[0]).toBe('HAS_STOP');
expect(lines[1]).toBe('HAS_PORT');
@@ -237,7 +237,7 @@ describe('bun-polyfill', () => {
require(${JSON.stringify(polyfillPath)});
Bun.spawn(['node', '-e', ''], { stdio: ['ignore', 'ignore', 'ignore'] });
console.log('windowsHide:' + seen.windowsHide);
`], { stdout: 'pipe', stderr: 'pipe' });
`], { stdout: 'pipe', stderr: 'pipe', timeout: 30_000 });
expect(result.stdout.toString().trim()).toBe('windowsHide:true');
});
@@ -250,7 +250,7 @@ describe('bun-polyfill', () => {
require(${JSON.stringify(polyfillPath)});
Bun.spawnSync(['node', '-e', '']);
console.log('windowsHide:' + seen.windowsHide);
`], { stdout: 'pipe', stderr: 'pipe' });
`], { stdout: 'pipe', stderr: 'pipe', timeout: 30_000 });
expect(result.stdout.toString().trim()).toBe('windowsHide:true');
});
@@ -263,7 +263,7 @@ describe('bun-polyfill', () => {
require(${JSON.stringify(polyfillPath)});
Bun.spawn(['node', '-e', ''], { stdio: ['ignore', 'ignore', 'ignore'], windowsHide: false });
console.log('windowsHide:' + seen.windowsHide);
`], { stdout: 'pipe', stderr: 'pipe' });
`], { stdout: 'pipe', stderr: 'pipe', timeout: 30_000 });
expect(result.stdout.toString().trim()).toBe('windowsHide:false');
});
});
+8 -8
View File
@@ -85,7 +85,7 @@ describe('config', () => {
// it (the exact bug the fix removed) would skip the guard here.
const tmpDir = path.join(os.tmpdir(), `browse-gitignored-repo-test-${Date.now()}`);
fs.mkdirSync(tmpDir, { recursive: true });
Bun.spawnSync(['git', 'init'], { cwd: tmpDir, stdout: 'ignore', stderr: 'ignore' });
Bun.spawnSync(['git', 'init'], { cwd: tmpDir, stdout: 'ignore', stderr: 'ignore', timeout: 30_000 });
fs.writeFileSync(path.join(tmpDir, '.gitignore'), '.gstack/\n');
const config = resolveConfig({ BROWSE_STATE_FILE: path.join(tmpDir, '.gstack', 'browse.json') });
ensureStateDir(config);
@@ -166,22 +166,22 @@ describe('config', () => {
fs.mkdirSync(tmpDir, { recursive: true });
// Set up a real git repo
spawnSync('git', ['init', '-q'], { cwd: tmpDir });
spawnSync('git', ['config', 'user.email', 'test@test.com'], { cwd: tmpDir });
spawnSync('git', ['config', 'user.name', 'Test'], { cwd: tmpDir });
spawnSync('git', ['init', '-q'], { cwd: tmpDir, timeout: 30_000 });
spawnSync('git', ['config', 'user.email', 'test@test.com'], { cwd: tmpDir, timeout: 30_000 });
spawnSync('git', ['config', 'user.name', 'Test'], { cwd: tmpDir, timeout: 30_000 });
// Write a global excludes file that ignores .gstack/
const excludesFile = path.join(tmpDir, 'global-gitignore');
fs.writeFileSync(excludesFile, '.gstack/\n');
spawnSync('git', ['config', 'core.excludesFile', excludesFile], { cwd: tmpDir });
spawnSync('git', ['config', 'core.excludesFile', excludesFile], { cwd: tmpDir, timeout: 30_000 });
// .gitignore exists but does NOT contain .gstack/
fs.writeFileSync(path.join(tmpDir, '.gitignore'), 'node_modules/\n');
spawnSync('git', ['add', '.gitignore'], { cwd: tmpDir });
spawnSync('git', ['commit', '-qm', 'init'], { cwd: tmpDir });
spawnSync('git', ['add', '.gitignore'], { cwd: tmpDir, timeout: 30_000 });
spawnSync('git', ['commit', '-qm', 'init'], { cwd: tmpDir, timeout: 30_000 });
// Verify git knows .gstack/ is ignored
const check = spawnSync('git', ['check-ignore', '-q', '.gstack/'], { cwd: tmpDir });
const check = spawnSync('git', ['check-ignore', '-q', '.gstack/'], { cwd: tmpDir, timeout: 30_000 });
expect(check.status).toBe(0);
const config = resolveConfig({ BROWSE_STATE_FILE: path.join(tmpDir, '.gstack', 'browse.json') });
+2 -2
View File
@@ -87,7 +87,7 @@ describe('restrictDirectoryPermissions', () => {
fs.mkdirSync(d);
// System chmod, not fs.chmodSync: Bun masks the sticky bit off chmod/
// mkdir modes, so 0o1777 through the fs API lands as 0o777.
Bun.spawnSync(['chmod', '1777', d]);
Bun.spawnSync(['chmod', '1777', d], { timeout: 30_000 });
expect(fs.statSync(d).mode & 0o7777).toBe(0o1777); // fixture took
restrictDirectoryPermissions(d);
expect(fs.statSync(d).mode & 0o7777).toBe(0o1777);
@@ -244,7 +244,7 @@ describe('mkdirSecure', () => {
fs.mkdirSync(d);
// System chmod: Bun's fs API masks the sticky bit off modes (see the
// restrictDirectoryPermissions sticky-dir test).
Bun.spawnSync(['chmod', '1777', d]);
Bun.spawnSync(['chmod', '1777', d], { timeout: 30_000 });
expect(fs.statSync(d).mode & 0o7777).toBe(0o1777); // fixture took
mkdirSecure(d);
expect(fs.statSync(d).mode & 0o7777).toBe(0o1777);
+3 -3
View File
@@ -160,7 +160,7 @@ describe('findPort / isPortAvailable', () => {
}
test();
`], { stdout: 'pipe', stderr: 'pipe' });
`], { stdout: 'pipe', stderr: 'pipe', timeout: 30_000 });
const output = result.stdout.toString().trim();
// Confirms the polyfill's stop() is fire-and-forget — callers
@@ -171,7 +171,7 @@ describe('findPort / isPortAvailable', () => {
test('net.createServer approach does not have the race condition', async () => {
// Prove the fix: net.createServer with proper async bind/close
// releases the port cleanly
const result = Bun.spawnSync(['node', '-e', `
const result = Bun.spawnSync(['node', '-e', ` // timeout in trailing options
const net = require('net');
async function testFix() {
@@ -205,7 +205,7 @@ describe('findPort / isPortAvailable', () => {
}
testFix();
`], { stdout: 'pipe', stderr: 'pipe' });
`], { stdout: 'pipe', stderr: 'pipe', timeout: 30_000 });
const output = result.stdout.toString().trim();
expect(output).toBe('FIX_WORKS');
+1
View File
@@ -31,6 +31,7 @@ function run(args: string[] = [], extraEnv: Record<string, string> = {}) {
env,
stdout: 'pipe',
stderr: 'pipe',
timeout: 30_000,
});
return {
exitCode: result.exitCode,
+1
View File
@@ -34,6 +34,7 @@ function run(extraEnv: Record<string, string> = {}, args: string[] = []) {
env,
stdout: 'pipe',
stderr: 'pipe',
timeout: 30_000,
});
return {
exitCode: result.exitCode,
+1 -1
View File
@@ -107,7 +107,7 @@ describe('untrustable TMPDIR values never widen the allowlist', () => {
const r = Bun.spawnSync([
process.execPath, '-e',
"import { TEMP_DIRS } from './browse/src/platform'; console.log(JSON.stringify(TEMP_DIRS));",
], { env: { ...process.env, TMPDIR: tmpdir }, cwd: path.resolve(import.meta.dir, '..', '..') });
], { env: { ...process.env, TMPDIR: tmpdir }, cwd: path.resolve(import.meta.dir, '..', '..'), timeout: 30_000 });
return JSON.parse(r.stdout.toString().trim().split('\n').pop()!);
};
+1 -1
View File
@@ -11,7 +11,7 @@ import {
const HAS_XVFB = (() => {
if (process.platform !== 'linux') return false;
const result = Bun.spawnSync(['which', 'Xvfb'], { stdout: 'pipe', stderr: 'pipe' });
const result = Bun.spawnSync(['which', 'Xvfb'], { stdout: 'pipe', stderr: 'pipe', timeout: 30_000 });
return result.exitCode === 0;
})();
+2 -1
View File
@@ -14,7 +14,7 @@ const MINT_BIN = join(ROOT, 'bin', 'gstack-ios-qa-mint');
const DAEMON_BIN = join(ROOT, 'bin', 'gstack-ios-qa-daemon');
function runMint(args: string[]) {
return spawnSync(MINT_BIN, args, { stdio: 'pipe', encoding: 'utf-8' });
return spawnSync(MINT_BIN, args, { stdio: 'pipe', encoding: 'utf-8', timeout: 30_000 });
}
describe('bin/gstack-ios-qa-mint launcher', () => {
@@ -112,6 +112,7 @@ describe('bin/gstack-ios-qa-daemon launcher', () => {
stdio: 'pipe',
encoding: 'utf-8',
env: { PATH: '/usr/bin:/bin' },
timeout: 30_000,
});
expect(r.status).not.toBe(0);
expect(r.stderr).toContain('bun');
+9 -7
View File
@@ -612,6 +612,7 @@ class AppState {
'--output', outputDir,
], {
encoding: 'utf8',
timeout: 30_000,
env: { ...process.env, GSTACK_IOS_CACHE_ROOT: join(workDir, 'cache') },
});
expect(result.status).toBe(4);
@@ -717,7 +718,7 @@ describe('render', () => {
});
test('typechecks beside an internal @Observable app state using a comment marker', () => {
if (spawnSync('swiftc', ['--version'], { encoding: 'utf8' }).status !== 0) return;
if (spawnSync('swiftc', ['--version'], { encoding: 'utf8', timeout: 30_000 }).status !== 0) return;
const coreSource = join(workDir, 'DebugBridgeCore.swift');
const coreModule = join(workDir, 'DebugBridgeCore.swiftmodule');
@@ -752,7 +753,7 @@ public final class StateServer {
'-module-name', 'DebugBridgeCore',
coreSource,
'-emit-module-path', coreModule,
], { encoding: 'utf8' });
], { encoding: 'utf8', timeout: 120_000 });
if (emitModule.status !== 0) {
throw new Error(`failed to build DebugBridgeCore test stub:\n${emitModule.stderr}`);
}
@@ -775,7 +776,7 @@ ${render([{
'-D', 'DEBUG',
'-I', workDir,
appSource,
], { encoding: 'utf8' });
], { encoding: 'utf8', timeout: 120_000 });
if (typecheck.status !== 0) {
throw new Error(`generated accessor failed Swift type checking:\n${typecheck.stderr}`);
}
@@ -783,7 +784,7 @@ ${render([{
test('strict JSON typing and cross-model validate-before-apply restore run correctly', () => {
if (process.platform !== 'darwin') return;
if (spawnSync('swiftc', ['--version'], { encoding: 'utf8' }).status !== 0) return;
if (spawnSync('swiftc', ['--version'], { encoding: 'utf8', timeout: 30_000 }).status !== 0) return;
const coreSource = join(workDir, 'DebugBridgeCore.swift');
const coreModule = join(workDir, 'DebugBridgeCore.swiftmodule');
@@ -830,7 +831,7 @@ public final class StateServer {
'-module-name', 'DebugBridgeCore', coreSource,
'-emit-module-path', coreModule,
'-o', coreLibrary,
], { encoding: 'utf8' });
], { encoding: 'utf8', timeout: 120_000 });
if (emitCore.status !== 0) throw new Error(`failed to build runtime stub:\n${emitCore.stderr}`);
const appSource = join(workDir, 'OptionalRoundTrip.swift');
@@ -915,10 +916,11 @@ struct Runner {
const compile = spawnSync('swiftc', [
'-D', 'DEBUG', '-I', workDir, '-L', workDir, '-lDebugBridgeCore',
'-parse-as-library', appSource, '-o', executable,
], { encoding: 'utf8' });
], { encoding: 'utf8', timeout: 120_000 });
if (compile.status !== 0) throw new Error(`generated Optional accessor failed compilation:\n${compile.stderr}`);
const run = spawnSync(executable, [], {
encoding: 'utf8',
timeout: 30_000,
env: { ...process.env, DYLD_LIBRARY_PATH: workDir },
});
if (run.status !== 0) throw new Error(`generated Optional accessor failed at runtime:\n${run.stderr}`);
@@ -928,7 +930,7 @@ struct Runner {
describe('SwiftSyntax generator parity', () => {
test('isolates canonical markers and rejects inaccessible fields', () => {
if (process.platform !== 'darwin') return;
if (spawnSync('swift', ['--version'], { encoding: 'utf8' }).status !== 0) return;
if (spawnSync('swift', ['--version'], { encoding: 'utf8', timeout: 30_000 }).status !== 0) return;
const packageDir = join(import.meta.dir, 'gen-accessors-tool');
const inputDir = join(workDir, 'swift-syntax-input');
+2 -1
View File
@@ -739,7 +739,7 @@ export function render(specs: AccessorSpec[], buildId: string, accessorHash: str
function detectSwiftVersion(): string {
if (process.env.SWIFT_VERSION) return process.env.SWIFT_VERSION;
try {
const out = execSync('swift --version', { stdio: ['ignore', 'pipe', 'ignore'] }).toString();
const out = execSync('swift --version', { stdio: ['ignore', 'pipe', 'ignore'], timeout: 30_000 }).toString();
const m = out.match(/Apple Swift version (\d+\.\d+\.\d+)/);
if (m) return m[1]!;
} catch {
@@ -754,6 +754,7 @@ function detectToolGitRev(): string {
return execSync('git rev-parse --short HEAD', {
cwd: dirname(new URL(import.meta.url).pathname),
stdio: ['ignore', 'pipe', 'ignore'],
timeout: 30_000,
}).toString().trim();
} catch {
return 'dev';
+1
View File
@@ -83,6 +83,7 @@ describe("diagram render gate", () => {
env: { ...process.env, BROWSE_BIN },
stdout: "pipe",
stderr: "pipe",
timeout: 120_000,
});
const stderr = new TextDecoder().decode(run.stderr);
if (run.exitCode !== 0) {
+2
View File
@@ -22,6 +22,7 @@ function runMigration(fakeHome: string): { code: number; stdout: string; stderr:
env: { ...process.env, HOME: fakeHome },
stdout: 'pipe',
stderr: 'pipe',
timeout: 30_000,
});
return {
code: proc.exitCode ?? -1,
@@ -217,6 +218,7 @@ function runMigrationV140(fakeHome: string): { code: number; stdout: string; std
env: { ...process.env, HOME: fakeHome },
stdout: 'pipe',
stderr: 'pipe',
timeout: 30_000,
});
return {
code: proc.exitCode ?? -1,
+1
View File
@@ -82,6 +82,7 @@ function runHook(stdin: object, env: Record<string, string>): { additionalContex
input: JSON.stringify(stdin),
encoding: 'utf-8',
env: { PATH: process.env.PATH ?? '/usr/bin:/bin', ...env },
timeout: 30_000,
});
const parsed = JSON.parse(res.stdout || '{}');
return parsed.hookSpecificOutput ?? {};
+21 -21
View File
@@ -69,8 +69,8 @@ describe("native slug fallback mirrors bin/gstack-slug", () => {
["https://gitlab.com/acme/Widget", "acme-Widget"],
] as const) {
const cwd = fs.mkdtempSync(path.join(tmp, "repo-"));
spawnSync("git", ["init", "-q"], { cwd });
spawnSync("git", ["remote", "add", "origin", url], { cwd });
spawnSync("git", ["init", "-q"], { cwd, timeout: 30_000 });
spawnSync("git", ["remote", "add", "origin", url], { cwd, timeout: 30_000 });
expect(slugFromEnvironment(path.join(tmp, "home2"), cwd)).toBe(want);
}
});
@@ -231,8 +231,8 @@ describe("walk-up parity with bin/gstack-slug (outermost project root)", () => {
const projectRoot = path.join(tmp, "realgit");
const subdir = path.join(projectRoot, "src", "deep");
fs.mkdirSync(subdir, { recursive: true });
spawnSync("git", ["init", "-q", projectRoot]);
spawnSync("git", ["-C", projectRoot, "remote", "add", "origin", "https://github.com/foo/bar.git"]);
spawnSync("git", ["init", "-q", projectRoot], { timeout: 30_000 });
spawnSync("git", ["-C", projectRoot, "remote", "add", "origin", "https://github.com/foo/bar.git"], { timeout: 30_000 });
expectBoth(subdir, "foo-bar");
});
@@ -269,10 +269,10 @@ describe("walk-up parity with bin/gstack-slug (outermost project root)", () => {
const outer = path.join(tmp, "outer-project");
const inner = path.join(outer, "vendor", "inner-lib");
fs.mkdirSync(inner, { recursive: true });
spawnSync("git", ["init", "-q", outer]);
spawnSync("git", ["-C", outer, "remote", "add", "origin", "git@github.com:acme/outer.git"]);
spawnSync("git", ["init", "-q", inner]);
spawnSync("git", ["-C", inner, "remote", "add", "origin", "git@github.com:vendor/inner.git"]);
spawnSync("git", ["init", "-q", outer], { timeout: 30_000 });
spawnSync("git", ["-C", outer, "remote", "add", "origin", "git@github.com:acme/outer.git"], { timeout: 30_000 });
spawnSync("git", ["init", "-q", inner], { timeout: 30_000 });
spawnSync("git", ["-C", inner, "remote", "add", "origin", "git@github.com:vendor/inner.git"], { timeout: 30_000 });
expectBoth(inner, "acme-outer");
});
@@ -286,8 +286,8 @@ describe("walk-up parity with bin/gstack-slug (outermost project root)", () => {
fs.mkdirSync(path.join(strayHome, ".git"), { recursive: true }); // empty — invalid repo
const repo = path.join(strayHome, "work", "repo");
fs.mkdirSync(repo, { recursive: true });
spawnSync("git", ["init", "-q", repo]);
spawnSync("git", ["-C", repo, "remote", "add", "origin", "https://github.com/garrytan/gstack"]);
spawnSync("git", ["init", "-q", repo], { timeout: 30_000 });
spawnSync("git", ["-C", repo, "remote", "add", "origin", "https://github.com/garrytan/gstack"], { timeout: 30_000 });
expectBoth(repo, "garrytan-gstack");
expect(slugFromEnvironment(nativeHome(), repo)).not.toBe("strayhome");
});
@@ -299,9 +299,9 @@ describe("walk-up parity with bin/gstack-slug (outermost project root)", () => {
const outer = path.join(tmp, "outer-plain");
const inner = path.join(outer, "vendor", "inner-lib");
fs.mkdirSync(inner, { recursive: true });
spawnSync("git", ["init", "-q", outer]); // no origin — marker-only repo
spawnSync("git", ["init", "-q", inner]);
spawnSync("git", ["-C", inner, "remote", "add", "origin", "git@github.com:vendor/inner.git"]);
spawnSync("git", ["init", "-q", outer], { timeout: 30_000 }); // no origin — marker-only repo
spawnSync("git", ["init", "-q", inner], { timeout: 30_000 });
spawnSync("git", ["-C", inner, "remote", "add", "origin", "git@github.com:vendor/inner.git"], { timeout: 30_000 });
expectBoth(inner, "vendor-inner");
});
@@ -313,8 +313,8 @@ describe("walk-up parity with bin/gstack-slug (outermost project root)", () => {
fs.mkdirSync(path.join(strayHome, ".git"), { recursive: true });
const repo = path.join(strayHome, "git", "proj");
fs.mkdirSync(repo, { recursive: true });
spawnSync("git", ["init", "-q", repo]);
spawnSync("git", ["-C", repo, "remote", "add", "origin", "https://github.com/garrytan/gstack"]);
spawnSync("git", ["init", "-q", repo], { timeout: 30_000 });
spawnSync("git", ["-C", repo, "remote", "add", "origin", "https://github.com/garrytan/gstack"], { timeout: 30_000 });
const cacheDir = path.join(nativeHome(), "slug-cache");
fs.mkdirSync(cacheDir, { recursive: true });
@@ -335,8 +335,8 @@ describe("walk-up parity with bin/gstack-slug (outermost project root)", () => {
const inner = path.join(wrapper, "apps", "web");
fs.mkdirSync(inner, { recursive: true });
fs.writeFileSync(path.join(wrapper, "package.json"), '{"name":"wrapper"}\n');
spawnSync("git", ["init", "-q", inner]);
spawnSync("git", ["-C", inner, "remote", "add", "origin", "https://github.com/acme/web.git"]);
spawnSync("git", ["init", "-q", inner], { timeout: 30_000 });
spawnSync("git", ["-C", inner, "remote", "add", "origin", "https://github.com/acme/web.git"], { timeout: 30_000 });
const cacheDir = path.join(nativeHome(), "slug-cache");
fs.mkdirSync(cacheDir, { recursive: true });
@@ -361,8 +361,8 @@ describe("walk-up parity with bin/gstack-slug (outermost project root)", () => {
// fire even though cached == basename(project root).
const repo = path.join(tmp, "stickyproj");
fs.mkdirSync(repo, { recursive: true });
spawnSync("git", ["init", "-q", repo]);
spawnSync("git", ["-C", repo, "remote", "add", "origin", "https://github.com/x/y.git"]);
spawnSync("git", ["init", "-q", repo], { timeout: 30_000 });
spawnSync("git", ["-C", repo, "remote", "add", "origin", "https://github.com/x/y.git"], { timeout: 30_000 });
const cacheDir = path.join(nativeHome(), "slug-cache");
fs.mkdirSync(cacheDir, { recursive: true });
@@ -379,8 +379,8 @@ describe("walk-up parity with bin/gstack-slug (outermost project root)", () => {
// implementations must reject it and fall through to the basename.
const repo = path.join(tmp, "dotty");
fs.mkdirSync(repo, { recursive: true });
spawnSync("git", ["init", "-q", repo]);
spawnSync("git", ["-C", repo, "remote", "add", "origin", ".."]);
spawnSync("git", ["init", "-q", repo], { timeout: 30_000 });
spawnSync("git", ["-C", repo, "remote", "add", "origin", ".."], { timeout: 30_000 });
expectBoth(repo, "dotty");
});
+2 -2
View File
@@ -102,7 +102,7 @@ describe('content-binding template drift', () => {
const abort = spawnSync('bash', ['-c', scriptFor(
'clean body\n',
'body with UNTRUSTED TRACKER CONTENT banner leak\n',
)], { encoding: 'utf-8' });
)], { encoding: 'utf-8', timeout: 30_000 });
expect(abort.stderr).toContain('ABORT: envelope banner leaked');
expect(abort.stdout).not.toContain('banner tripwire clean');
@@ -110,7 +110,7 @@ describe('content-binding template drift', () => {
const clean = spawnSync('bash', ['-c', scriptFor(
'clean body\n',
'also clean body\n',
)], { encoding: 'utf-8' });
)], { encoding: 'utf-8', timeout: 30_000 });
expect(clean.stdout).toContain('banner tripwire clean');
expect(clean.stderr).not.toContain('ABORT');
} finally {
+13 -11
View File
@@ -43,12 +43,13 @@ function run(argv: string[], opts: { env?: Record<string, string>; input?: strin
encoding: 'utf-8',
input: opts.input,
cwd: ROOT,
timeout: 30_000,
});
return { stdout: res.stdout || '', stderr: res.stderr || '', status: res.status ?? -1 };
}
function git(args: string[], cwd?: string) {
const res = spawnSync('git', args, { cwd: cwd || tmpHome, encoding: 'utf-8' });
const res = spawnSync('git', args, { cwd: cwd || tmpHome, encoding: 'utf-8', timeout: 30_000 });
return { stdout: res.stdout || '', stderr: res.stderr || '', status: res.status ?? -1 };
}
@@ -77,7 +78,7 @@ function seedSpool(record: string): string {
beforeEach(() => {
tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'brain-sync-home-'));
bareRemote = fs.mkdtempSync(path.join(os.tmpdir(), 'brain-sync-remote-'));
spawnSync('git', ['init', '--bare', '-q', '-b', 'main', bareRemote]);
spawnSync('git', ['init', '--bare', '-q', '-b', 'main', bareRemote], { timeout: 30_000 });
});
afterEach(() => {
@@ -198,6 +199,7 @@ describe('gstack-brain-enqueue', () => {
const r = spawnSync(path.join(BIN, 'gstack-brain-enqueue'), [`file-${i}.jsonl`], {
env: { ...process.env, GSTACK_HOME: tmpHome },
encoding: 'utf-8',
timeout: 30_000,
});
resolve();
}));
@@ -245,7 +247,7 @@ describe('gstack-jsonl-merge', () => {
const lines = fs.readFileSync(ours, 'utf-8').trim().split('\n');
expect(lines.length).toBe(3);
// Order is deterministic (sha256 of each line).
const again = spawnSync(path.join(BIN, 'gstack-jsonl-merge'), [base, ours, theirs]);
const again = spawnSync(path.join(BIN, 'gstack-jsonl-merge'), [base, ours, theirs], { timeout: 30_000 });
// (re-running doesn't change the order since same input → same output)
});
});
@@ -271,7 +273,7 @@ describe('init + sync + restore round-trip', () => {
test('refuses init on different remote', () => {
run(['gstack-artifacts-init', '--remote', bareRemote]);
const otherRemote = fs.mkdtempSync(path.join(os.tmpdir(), 'brain-other-'));
spawnSync('git', ['init', '--bare', '-q', '-b', 'main', otherRemote]);
spawnSync('git', ['init', '--bare', '-q', '-b', 'main', otherRemote], { timeout: 30_000 });
const r = run(['gstack-artifacts-init', '--remote', otherRemote]);
expect(r.status).not.toBe(0);
expect(r.stderr).toContain('already a git repo pointing at');
@@ -288,7 +290,7 @@ describe('init + sync + restore round-trip', () => {
const r = run(['gstack-brain-sync', '--once']);
expect(r.status).toBe(0);
// Check the remote got the commit.
const log = spawnSync('git', ['--git-dir=' + bareRemote, 'log', '--oneline'], { encoding: 'utf-8' });
const log = spawnSync('git', ['--git-dir=' + bareRemote, 'log', '--oneline'], { encoding: 'utf-8', timeout: 30_000 });
expect(log.stdout).toMatch(/sync: 1 file/);
});
@@ -311,7 +313,7 @@ describe('init + sync + restore round-trip', () => {
const restored = fs.readFileSync(path.join(machineB, 'projects/myproj/learnings.jsonl'), 'utf-8');
expect(restored).toContain('machine A wisdom');
// Merge drivers re-registered on B.
const cfg = spawnSync('git', ['-C', machineB, 'config', '--get', 'merge.jsonl-append.driver'], { encoding: 'utf-8' });
const cfg = spawnSync('git', ['-C', machineB, 'config', '--get', 'merge.jsonl-append.driver'], { encoding: 'utf-8', timeout: 30_000 });
expect(cfg.stdout).toContain('gstack-jsonl-merge');
fs.rmSync(machineB, { recursive: true, force: true });
});
@@ -399,7 +401,7 @@ describe('gstack-brain-sync egress receipt gate', () => {
// No local commit was created.
expect(git(['rev-list', '--count', 'HEAD']).stdout.trim()).toBe(commitsBefore);
// Nothing reached the remote.
const remoteLog = spawnSync('git', ['--git-dir=' + bareRemote, 'log', '--oneline'], { encoding: 'utf-8' });
const remoteLog = spawnSync('git', ['--git-dir=' + bareRemote, 'log', '--oneline'], { encoding: 'utf-8', timeout: 30_000 });
expect(remoteLog.stdout).not.toMatch(/sync: 1 file/);
const status = JSON.parse(fs.readFileSync(path.join(tmpHome, '.brain-sync-status.json'), 'utf-8'));
expect(status.status).toBe('push_failed');
@@ -411,7 +413,7 @@ describe('gstack-brain-sync egress receipt gate', () => {
// Next run (ledger writable again) drains the intact queue and pushes.
const retry = run(['gstack-brain-sync', '--once']);
expect(retry.status).toBe(0);
const log = spawnSync('git', ['--git-dir=' + bareRemote, 'log', '--oneline'], { encoding: 'utf-8' });
const log = spawnSync('git', ['--git-dir=' + bareRemote, 'log', '--oneline'], { encoding: 'utf-8', timeout: 30_000 });
expect(log.stdout).toMatch(/sync: 1 file/);
});
@@ -583,7 +585,7 @@ describe('#2549 queue integrity', () => {
expect(r.status).toBe(0);
expect(spoolText()).not.toContain('learnings.jsonl'); // synced, removed
expect(spoolText()).toContain('timeline.jsonl'); // held, retained
const log = spawnSync('git', ['--git-dir=' + bareRemote, 'log', '--oneline'], { encoding: 'utf-8' });
const log = spawnSync('git', ['--git-dir=' + bareRemote, 'log', '--oneline'], { encoding: 'utf-8', timeout: 30_000 });
expect(log.stdout).toMatch(/sync: 1 file/);
});
@@ -619,7 +621,7 @@ describe('#2549 queue integrity', () => {
fs.rmSync(hook);
const retry = run(['gstack-brain-sync', '--once']);
expect(retry.status).toBe(0);
const log = spawnSync('git', ['--git-dir=' + bareRemote, 'log', '--oneline'], { encoding: 'utf-8' });
const log = spawnSync('git', ['--git-dir=' + bareRemote, 'log', '--oneline'], { encoding: 'utf-8', timeout: 30_000 });
expect(log.stdout).toMatch(/sync: 1 file/);
expect(git(['rev-list', '--count', 'origin/main..HEAD']).stdout.trim()).toBe('0');
});
@@ -751,7 +753,7 @@ describe('C12 spool queue', () => {
run(['gstack-config', 'set', 'artifacts_sync_mode', mode]);
}
const remoteLog = () =>
spawnSync('git', ['--git-dir=' + bareRemote, 'log', '--oneline'], { encoding: 'utf-8' }).stdout;
spawnSync('git', ['--git-dir=' + bareRemote, 'log', '--oneline'], { encoding: 'utf-8', timeout: 30_000 }).stdout;
test('two rapid enqueues of different paths create two spool files; one drain syncs both', () => {
initWithMode('full');
+5 -5
View File
@@ -37,7 +37,7 @@ const FILENAME_PREFIX = /\$\{?_BRANCH\}?[A-Za-z0-9._-]*\.(?:jsonl|json|md|txt|lo
function renderedSkillFiles(): string[] {
const out = execSync(
`find "${ROOT}" -name 'SKILL.md' -not -path '*/node_modules/*' -not -path '*/.claude/*' ; find "${ROOT}" -path '*/sections/*.md' -not -path '*/node_modules/*' -not -path '*/.claude/*'`,
{ encoding: 'utf-8' },
{ encoding: 'utf-8', timeout: 30_000 },
);
return out.split('\n').filter(Boolean);
}
@@ -91,18 +91,18 @@ describe('branch slug hygiene (#2550, #1851)', () => {
const env = { ...process.env, GSTACK_HOME: home };
execSync(
'git init -q && git -c user.email=t@t -c user.name=t commit -q --allow-empty -m init && git checkout -q -b feat/slug-hygiene',
{ cwd: repo, encoding: 'utf-8' },
{ cwd: repo, encoding: 'utf-8', timeout: 30_000 },
);
// Writer: the real gstack-review-log (canonicalizes via gstack-slug).
execSync(
`"${path.join(ROOT, 'bin', 'gstack-review-log')}" '{"skill":"ship","status":"ok"}'`,
{ cwd: repo, env, encoding: 'utf-8' },
{ cwd: repo, env, encoding: 'utf-8', timeout: 30_000 },
);
// The slug-canonical filename must exist; the raw form must not.
const slugVars = execSync(`"${path.join(ROOT, 'bin', 'gstack-slug')}"`, {
cwd: repo, env, encoding: 'utf-8',
cwd: repo, env, encoding: 'utf-8', timeout: 30_000,
});
const slug = slugVars.match(/^SLUG=(.*)$/m)![1];
const branch = slugVars.match(/^BRANCH=(.*)$/m)![1];
@@ -120,7 +120,7 @@ describe('branch slug hygiene (#2550, #1851)', () => {
.find((l) => l.includes('-reviews.jsonl'))!;
const script = `_PROJ="${proj}"\nBRANCH="${branch}"\n${probeLine.trim()}`;
const out = execSync(`bash -c '${script.replace(/'/g, `'\\''`)}'`, {
cwd: repo, encoding: 'utf-8',
cwd: repo, encoding: 'utf-8', timeout: 30_000,
});
expect(out).toContain('REVIEWS: 1 entries');
+1 -1
View File
@@ -16,7 +16,7 @@ function runProfile(): Record<string, string> {
encoding: 'utf-8',
timeout: 15000,
};
const stdout = execSync(`${BIN}/gstack-builder-profile`, execOpts).trim();
const stdout = execSync(`${BIN}/gstack-builder-profile`, execOpts).trim(); // timeout via execOpts
const result: Record<string, string> = {};
for (const line of stdout.split('\n')) {
const idx = line.indexOf(':');
+4 -4
View File
@@ -153,9 +153,9 @@ describe("session-start indexing offer (suggest)", () => {
home = fs.mkdtempSync(path.join(os.tmpdir(), "ci-home-"));
repo = fs.mkdtempSync(path.join(os.tmpdir(), "ci-repo-"));
env = { ...process.env, GSTACK_HOME: home };
Bun.spawnSync(["git", "init", "-q", repo]);
Bun.spawnSync(["git", "init", "-q", repo], { timeout: 30_000 });
for (const name of ["a.ts", "b.ts", "c.ts"]) fs.writeFileSync(path.join(repo, name), "x\n");
Bun.spawnSync(["git", "-C", repo, "add", "-A"]);
Bun.spawnSync(["git", "-C", repo, "add", "-A"], { timeout: 30_000 });
});
afterEach(() => {
fs.rmSync(home, { recursive: true, force: true });
@@ -971,9 +971,9 @@ exit 1
function makeRepoWithFiles(count: number): string {
const repo = fs.mkdtempSync(path.join(os.tmpdir(), "ci-cli-suggest-"));
Bun.spawnSync(["git", "init", "-q", repo]);
Bun.spawnSync(["git", "init", "-q", repo], { timeout: 30_000 });
for (let i = 0; i < count; i++) fs.writeFileSync(path.join(repo, `f${i}.ts`), "x\n");
Bun.spawnSync(["git", "-C", repo, "add", "-A"]);
Bun.spawnSync(["git", "-C", repo, "add", "-A"], { timeout: 30_000 });
return repo;
}
+1 -1
View File
@@ -43,7 +43,7 @@ const ROOT = path.resolve(import.meta.dir, '..');
const CODEX_AVAILABLE = (() => {
try {
const result = Bun.spawnSync(['which', 'codex']);
const result = Bun.spawnSync(['which', 'codex'], { timeout: 30_000 });
return result.exitCode === 0;
} catch { return false; }
})();
@@ -31,7 +31,7 @@ const ROOT = path.resolve(import.meta.dir, '..');
const CODEX_AVAILABLE = (() => {
try {
return Bun.spawnSync(['which', 'codex']).exitCode === 0;
return Bun.spawnSync(['which', 'codex'], { timeout: 30_000 }).exitCode === 0;
} catch {
return false;
}
+2 -2
View File
@@ -21,12 +21,12 @@ import { EvalCollector } from './helpers/eval-store';
import { selectTests, detectBaseBranch, getChangedFiles, GLOBAL_TOUCHFILES } from './helpers/touchfiles';
const ROOT = path.resolve(import.meta.dir, '..');
const CODEX_AVAILABLE = spawnSync('which', ['codex']).status === 0;
const CODEX_AVAILABLE = spawnSync('which', ['codex'], { timeout: 30_000 }).status === 0;
// The run pins the model with --ignore-user-config; older codex CLIs reject
// the flag with an argv error indistinguishable from a Sol regression, so
// probe support and skip (not fail) on old CLIs.
const IGNORE_USER_CONFIG_SUPPORTED = CODEX_AVAILABLE
&& (spawnSync('codex', ['exec', '--help'], { encoding: 'utf8' }).stdout ?? '').includes('--ignore-user-config');
&& (spawnSync('codex', ['exec', '--help'], { encoding: 'utf8', timeout: 120_000 }).stdout ?? '').includes('--ignore-user-config');
const evalsEnabled = !!process.env.EVALS;
// External-service test — periodic tier only (CLAUDE.md tiering rule 3). The
// positive guard shape below is what classifyPaidTestFile greps to exclude
+1 -1
View File
@@ -32,7 +32,7 @@ const ROOT = path.resolve(import.meta.dir, '..');
const CODEX_AVAILABLE = (() => {
try {
const result = Bun.spawnSync(['which', 'codex']);
const result = Bun.spawnSync(['which', 'codex'], { timeout: 30_000 });
return result.exitCode === 0;
} catch { return false; }
})();
+2
View File
@@ -116,6 +116,7 @@ model = "gpt-5.6-terra"
cwd: ROOT,
encoding: 'utf8',
env: { ...process.env, CODEX_HOME: home },
timeout: 30_000,
});
expect(ok.status).toBe(0);
expect(ok.stdout).toBe(`gpt-5.6-sol\t${path.join(home, 'config.toml')}\n`);
@@ -123,6 +124,7 @@ model = "gpt-5.6-terra"
const bad = spawnSync('bun', ['run', 'scripts/resolve-codex-generation-model.ts', '--explicit', 'llama-local'], {
cwd: ROOT,
encoding: 'utf8',
timeout: 30_000,
});
expect(bad.status).not.toBe(0);
expect(bad.stderr).toContain('Unknown model');
+1 -1
View File
@@ -297,7 +297,7 @@ describe('gstack-codex-probe: timeout wrapper + namespace hygiene', () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-watchdog-'));
try {
const which = (tool: string) =>
spawnSync('bash', ['-c', `command -v ${tool}`]).stdout.toString().trim() || `/bin/${tool}`;
spawnSync('bash', ['-c', `command -v ${tool}`], { timeout: 30_000 }).stdout.toString().trim() || `/bin/${tool}`;
fs.symlinkSync(which('bash'), path.join(dir, 'bash'));
fs.symlinkSync(which('sleep'), path.join(dir, 'sleep'));
const r = runProbe({
+1 -1
View File
@@ -15,7 +15,7 @@
import { describe, test, expect } from 'bun:test';
import { spawnSync } from 'child_process';
const codexPath = spawnSync('which', ['codex'], { encoding: 'utf-8' }).stdout.trim();
const codexPath = spawnSync('which', ['codex'], { encoding: 'utf-8', timeout: 30_000 }).stdout.trim();
const codexAvailable = codexPath.length > 0;
describe.skipIf(!codexAvailable)(
+1 -1
View File
@@ -23,7 +23,7 @@ function grepRepo(pattern: string, includes: string[]): string[] {
const includeArgs = includes.map((i) => `--include='${i}'`).join(' ');
const out = execSync(
`grep -rln ${includeArgs} -e '${pattern}' "${ROOT}" || true`,
{ encoding: 'utf-8' },
{ encoding: 'utf-8', timeout: 30_000 },
);
return out
.split('\n')
+1 -1
View File
@@ -718,7 +718,7 @@ describe("CLI plumbing", () => {
});
it("bin/gstack-context-bill runs standalone", () => {
const result = Bun.spawnSync([path.join(ROOT, "bin", "gstack-context-bill"), TREE_A]);
const result = Bun.spawnSync([path.join(ROOT, "bin", "gstack-context-bill"), TREE_A], { timeout: 30_000 });
expect(result.exitCode).toBe(0);
expect(result.stdout.toString()).toContain("ALWAYS-ON");
expect(result.stdout.toString()).toContain("EAGER");
+1 -1
View File
@@ -99,7 +99,7 @@ describe("normalizeIntFlag CLI wrapper (exit-1 semantics)", () => {
const v = normalizeIntFlag(${rawExpr}, ${specExpr});
console.log("VALUE:" + v);
`;
const res = spawnSync("bun", ["-e", script], { encoding: "utf-8", cwd: ROOT });
const res = spawnSync("bun", ["-e", script], { encoding: "utf-8", cwd: ROOT, timeout: 30_000 });
return { status: res.status ?? -1, stderr: res.stderr ?? "" };
}
+1 -1
View File
@@ -86,7 +86,7 @@ describe("diagram-render bundle drift", () => {
"deep: fresh build reproduces committed dist",
async () => {
const before = await Bun.file(BUILD_INFO).json();
const proc = Bun.spawnSync(["bun", "run", "scripts/build.ts"], { cwd: ROOT });
const proc = Bun.spawnSync(["bun", "run", "scripts/build.ts"], { cwd: ROOT, timeout: 120_000 });
expect(proc.exitCode).toBe(0);
const after = await Bun.file(BUILD_INFO).json();
expect(after.sha256).toBe(before.sha256);
+1 -1
View File
@@ -60,7 +60,7 @@ function run(args: string[]): { stdout: string; stderr: string; status: number }
env.GSTACK_STATE_ROOT = stateRoot;
env.GSTACK_QUESTION_LOG_NO_DERIVE = '1';
delete env.GSTACK_HOME;
const res = spawnSync(BIN, args, { env, encoding: 'utf-8', cwd: fixtureCwd });
const res = spawnSync(BIN, args, { env, encoding: 'utf-8', cwd: fixtureCwd, timeout: 30_000 });
return {
stdout: res.stdout ?? '',
stderr: res.stderr ?? '',
+5 -3
View File
@@ -47,6 +47,7 @@ function run(args: string[]): { stdout: string; stderr: string; status: number }
env: makeEnv(),
encoding: 'utf-8',
cwd: fixtureCwd,
timeout: 30_000,
});
return {
stdout: res.stdout ?? '',
@@ -75,6 +76,7 @@ function writeAuqOtherEvent(text: string): void {
env: makeEnv(),
cwd: fixtureCwd,
encoding: 'utf-8',
timeout: 30_000,
},
);
}
@@ -150,7 +152,7 @@ describe('no-event paths', () => {
tool_use_id: 'tu-x',
}),
],
{ env: makeEnv(), cwd: fixtureCwd, encoding: 'utf-8' },
{ env: makeEnv(), cwd: fixtureCwd, encoding: 'utf-8', timeout: 30_000 },
);
const r = run([]);
expect(r.status).toBe(0);
@@ -169,7 +171,7 @@ describe('--dry-run', () => {
// Strip ANTHROPIC_API_KEY to prove no API call happens.
const env = makeEnv();
delete env.ANTHROPIC_API_KEY;
const res = spawnSync(BIN, ['--dry-run'], { env, cwd: fixtureCwd, encoding: 'utf-8' });
const res = spawnSync(BIN, ['--dry-run'], { env, cwd: fixtureCwd, encoding: 'utf-8', timeout: 30_000 });
expect(res.status).toBe(0);
expect(res.stdout).toContain('DISTILL PROMPT');
expect(res.stdout).toContain('always include tests');
@@ -185,7 +187,7 @@ describe('API auth', () => {
writeAuqOtherEvent('Some free text response that needs distilling');
const env = makeEnv();
delete env.ANTHROPIC_API_KEY;
const res = spawnSync(BIN, [], { env, cwd: fixtureCwd, encoding: 'utf-8' });
const res = spawnSync(BIN, [], { env, cwd: fixtureCwd, encoding: 'utf-8', timeout: 30_000 });
expect(res.status).not.toBe(0);
expect(res.stderr).toMatch(/ANTHROPIC_API_KEY/);
expect(res.stderr).toMatch(/separate billing/);
+4 -4
View File
@@ -222,12 +222,12 @@ describe('gstack-egress-receipt shell bridge', () => {
fs.writeFileSync(payload, '[{"v":1}]');
const write = spawnSync(bin, ['write', '--sink', 'telemetry-sync', '--host', '127.0.0.1:8399',
'--class', 'telemetry-events', '--payload-file', payload, '--consent', 'telemetry=community'],
{ encoding: 'utf-8', env: { ...process.env, GSTACK_HOME: home } });
{ encoding: 'utf-8', timeout: 30_000, env: { ...process.env, GSTACK_HOME: home } });
expect(write.status).toBe(0);
const id = write.stdout.trim();
expect(id).toMatch(/^[0-9a-f]{64}$/);
const outcome = spawnSync(bin, ['outcome', id, '204'],
{ encoding: 'utf-8', env: { ...process.env, GSTACK_HOME: home } });
{ encoding: 'utf-8', timeout: 30_000, env: { ...process.env, GSTACK_HOME: home } });
expect(outcome.status).toBe(0);
const receipts = listReceipts(home);
expect(receipts.length).toBe(1);
@@ -239,7 +239,7 @@ describe('gstack-egress-receipt shell bridge', () => {
test('--no-payload records sha256:null (git-class: a subprocess owns the bytes)', () => {
const write = spawnSync(bin, ['write', '--sink', 'brain-sync', '--host', 'github.com',
'--class', 'git-push', '--no-payload', '--consent', 'artifacts_sync_mode=auto'],
{ encoding: 'utf-8', env: { ...process.env, GSTACK_HOME: home } });
{ encoding: 'utf-8', timeout: 30_000, env: { ...process.env, GSTACK_HOME: home } });
expect(write.status).toBe(0);
const receipts = listReceipts(home);
expect(receipts.length).toBe(1);
@@ -251,7 +251,7 @@ describe('gstack-egress-receipt shell bridge', () => {
if (!canRevokeWrites()) return; // chmod is advisory here (win32, root, DAC-override containers)
fs.mkdirSync(path.join(home, 'security'), { recursive: true, mode: 0o500 });
const write = spawnSync(bin, ['write', '--sink', 's', '--host', 'h', '--class', 'c', '--no-payload'],
{ encoding: 'utf-8', env: { ...process.env, GSTACK_HOME: home } });
{ encoding: 'utf-8', timeout: 30_000, env: { ...process.env, GSTACK_HOME: home } });
expect(write.status).toBe(3);
expect(write.stderr).toContain('EGRESS_RECEIPT_FAILED');
fs.chmodSync(path.join(home, 'security'), 0o700);
+3 -2
View File
@@ -35,7 +35,7 @@ describe('empty find must not fall through to cwd (#2483)', () => {
test('no resolver emits a bare `xargs ls -t` (must be `xargs -r ls -t`)', () => {
const out = execSync(
`grep -rn "xargs ls -t" "${path.join(ROOT, 'scripts')}" "${path.join(ROOT, 'bin')}" || true`,
{ encoding: 'utf-8' },
{ encoding: 'utf-8', timeout: 30_000 },
);
expect(out.trim()).toBe('');
});
@@ -67,6 +67,7 @@ describe('empty find must not fall through to cwd (#2483)', () => {
const out = execSync(`bash -c '${script.replace(/'/g, `'\\''`)}'`, {
cwd,
encoding: 'utf-8',
timeout: 30_000,
});
expect(out).toContain('LATEST_CP=[]');
expect(out).not.toContain('DECOY.md');
@@ -79,7 +80,7 @@ describe('empty find must not fall through to cwd (#2483)', () => {
test('no generated SKILL.md carries the unguarded form', () => {
const out = execSync(
`grep -rln "xargs ls -t" --include=SKILL.md "${ROOT}" || true`,
{ encoding: 'utf-8' },
{ encoding: 'utf-8', timeout: 30_000 },
);
// node_modules and vendored trees are not generated output; nothing in
// the repo's generated skills may carry the unguarded form.
+1 -1
View File
@@ -41,7 +41,7 @@ describe('eval budget tiers', () => {
});
test('no paid-test timeout literal exceeds the ceiling tier', () => {
const out = spawnSync('git', ['ls-files', 'test/*.test.ts'], { cwd: ROOT, encoding: 'utf-8' });
const out = spawnSync('git', ['ls-files', 'test/*.test.ts'], { cwd: ROOT, encoding: 'utf-8', timeout: 30_000 });
const files = out.stdout.split('\n').filter((f) => f && isPaidTestFile(f));
expect(files.length).toBeGreaterThan(50); // scan-rot guard
+1
View File
@@ -65,6 +65,7 @@ function runEvalList(...args: string[]): { stdout: string; stderr: string; statu
GSTACK_HOME: path.join(tmpHome, '.gstack'),
},
encoding: 'utf-8',
timeout: 30_000,
});
return {
stdout: result.stdout ?? '',
+4 -3
View File
@@ -74,11 +74,11 @@ describe("swept mkdirp sites under bun-on-Windows EEXIST semantics (#2635)", ()
fs.mkdirSync(work, { recursive: true });
const payload = '{"decision":"eexist probe","rationale":"r","scope":"repo","source":"user"}';
const env = { ...process.env, HOME: base };
const first = spawnSync("bun", [DECISION_LOG, payload], { cwd: work, encoding: "utf8", env });
const first = spawnSync("bun", [DECISION_LOG, payload], { cwd: work, encoding: "utf8", env, timeout: 30_000 });
expect(first.status).toBe(0);
const second = spawnSync(
"bun", ["--preload", EEXIST_PRELOAD, DECISION_LOG, payload],
{ cwd: work, encoding: "utf8", env },
{ cwd: work, encoding: "utf8", env, timeout: 30_000 },
);
expect(second.status).toBe(0);
expect(second.stderr ?? "").not.toContain("EEXIST");
@@ -93,7 +93,7 @@ describe("install-prepush-hook under bun-on-Windows EEXIST semantics (#2635)", (
const base = tmpdir();
try {
const repo = path.join(base, "repo");
spawnSync("git", ["init", "-q", repo]);
spawnSync("git", ["init", "-q", repo], { timeout: 30_000 });
const hookDir = path.join(repo, ".git", "hooks");
fs.mkdirSync(hookDir, { recursive: true });
const hookPath = path.join(hookDir, "pre-push");
@@ -105,6 +105,7 @@ describe("install-prepush-hook under bun-on-Windows EEXIST semantics (#2635)", (
const r = spawnSync("bun", ["--preload", EEXIST_PRELOAD, REDACT, "install-prepush-hook"], {
cwd: repo,
encoding: "utf8",
timeout: 30_000,
});
expect(r.status).toBe(0);
expect(r.stderr ?? "").not.toContain("EEXIST");
+1
View File
@@ -51,6 +51,7 @@ function run(bin: string, args: string[], opts: RunOpts = {}) {
env,
cwd: opts.cwd,
encoding: 'utf-8',
timeout: 30_000,
});
return {
stdout: (res.stdout || '').trim(),
+1
View File
@@ -122,6 +122,7 @@ echo "ok"
HOME: env.home,
PATH: `${env.bindir}:/usr/bin:/bin`,
},
timeout: 30_000,
});
return {
exitCode: result.status ?? 1,
+3 -1
View File
@@ -121,6 +121,7 @@ gbrain init --pglite --json "$@"
const result = spawnSync(shell, ["-c", script], {
encoding: "utf-8",
env: baseEnv,
timeout: 30_000,
});
if (result.status !== 0) {
throw new Error(`init script exited ${result.status}: ${result.stderr}`);
@@ -133,7 +134,7 @@ function lastArgc(env: FakeEnv): number {
return parseInt(lines[lines.length - 1], 10);
}
const HAVE_ZSH = spawnSync("zsh", ["-c", "true"]).status === 0;
const HAVE_ZSH = spawnSync("zsh", ["-c", "true"], { timeout: 30_000 }).status === 0;
describe("voyage-code-3 default for gstack-driven PGLite init", () => {
it("passes voyage-code-3 flags when VOYAGE_API_KEY is set", () => {
@@ -193,6 +194,7 @@ gbrain init --pglite --json $GBRAIN_EMBED_FLAGS
const result = spawnSync("zsh", ["-c", brokenShape], {
encoding: "utf-8",
env: { ...process.env, HOME: env.home, PATH: `${env.bindir}:/usr/bin:/bin` },
timeout: 30_000,
});
expect(result.status).toBe(0);
expect(lastArgc(env)).toBe(4); // init, --pglite, --json, "<entire flag string>"
+2
View File
@@ -28,6 +28,7 @@ function runVerify(arg: string, stdin?: string) {
const res = spawnSync(VERIFY, arg === '' ? [] : [arg], {
input: stdin,
encoding: 'utf-8',
timeout: 30_000,
});
return {
stdout: (res.stdout || '').trim(),
@@ -43,6 +44,7 @@ function runLibSnippet(snippet: string, stdin: string = '') {
const res = spawnSync('bash', ['-c', script], {
input: stdin,
encoding: 'utf-8',
timeout: 30_000,
});
return {
stdout: (res.stdout || '').trim(),
+3
View File
@@ -538,6 +538,7 @@ describe("lib/gbrain-local-status — thin-client (#2051)", () => {
GSTACK_HOME: env.gstackHome,
GSTACK_DETECT_NO_CACHE: "1",
},
timeout: 30_000,
});
expect(r.status).toBe(0);
});
@@ -554,6 +555,7 @@ describe("lib/gbrain-local-status — thin-client (#2051)", () => {
GSTACK_HOME: env.gstackHome,
GSTACK_DETECT_NO_CACHE: "1",
},
timeout: 30_000,
});
expect(r.status).toBe(1);
});
@@ -790,6 +792,7 @@ describe("lib/gbrain-local-status — bearer-token thin-client (#2520)", () => {
GSTACK_HOME: env.gstackHome,
GSTACK_DETECT_NO_CACHE: "1",
},
timeout: 30_000,
});
expect(r.status).toBe(0);
});
+1 -1
View File
@@ -32,7 +32,7 @@ function env(): NodeJS.ProcessEnv {
}
function run(args: string[], input?: string) {
const res = spawnSync(BIN, args, { env: env(), encoding: "utf-8", input });
const res = spawnSync(BIN, args, { env: env(), encoding: "utf-8", input, timeout: 30_000 });
return {
stdout: res.stdout || "",
stderr: res.stderr || "",
+3 -1
View File
@@ -29,6 +29,7 @@ function run(args: string[], opts: { env?: Record<string, string> } = {}) {
const res = spawnSync(BIN, args, {
env: { ...process.env, GSTACK_HOME: tmpHome, ...(opts.env || {}) },
encoding: 'utf-8',
timeout: 30_000,
});
return {
stdout: (res.stdout || '').trim(),
@@ -263,6 +264,7 @@ describe('get without arg (auto-detect from current dir)', () => {
env: { ...process.env, GSTACK_HOME: tmpHome },
cwd: cwdTmp,
encoding: 'utf-8',
timeout: 30_000,
});
expect((res.stdout || '').trim()).toBe('unset');
} finally {
@@ -288,7 +290,7 @@ describe('gstack-gbrain-sync code stage honors the repo policy (#2140 sync path)
function makeRepo(): void {
repoDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gbrain-policy-repo-'));
const git = (...args: string[]) =>
spawnSync('git', args, { cwd: repoDir, encoding: 'utf-8' });
spawnSync('git', args, { cwd: repoDir, encoding: 'utf-8', timeout: 30_000 });
git('init', '-q', '.');
git('remote', 'add', 'origin', REPO_URL);
fs.writeFileSync(path.join(repoDir, 'README.md'), 'fixture\n');
+2 -1
View File
@@ -23,12 +23,13 @@ function run(argv: string[], env: Record<string, string> = {}) {
env: { ...process.env, HOME: tmpHome, GSTACK_HOME: tmpHome, ...env },
encoding: 'utf-8',
cwd: ROOT,
timeout: 30_000,
});
return { stdout: res.stdout || '', stderr: res.stderr || '', status: res.status ?? -1 };
}
function git(args: string[], cwd: string) {
const res = spawnSync('git', args, { cwd, encoding: 'utf-8' });
const res = spawnSync('git', args, { cwd, encoding: 'utf-8', timeout: 30_000 });
return { stdout: (res.stdout || '').trim(), status: res.status ?? -1 };
}
+2 -1
View File
@@ -112,9 +112,10 @@ function runOrchestrator(
): { stdout: string; stderr: string; exitCode: number } {
// Initialize a git repo in the sandbox so repoRoot() finds it (otherwise
// code stage skips with "not in git repo" before our check ever fires).
spawnSync("git", ["init", "-q", env.home], { encoding: "utf-8" });
spawnSync("git", ["init", "-q", env.home], { encoding: "utf-8", timeout: 30_000 });
spawnSync("git", ["-C", env.home, "commit", "--allow-empty", "-m", "init", "-q"], {
encoding: "utf-8",
timeout: 30_000,
env: { ...process.env, GIT_AUTHOR_NAME: "T", GIT_AUTHOR_EMAIL: "t@t", GIT_COMMITTER_NAME: "T", GIT_COMMITTER_EMAIL: "t@t" },
});
@@ -44,7 +44,7 @@ import { tmpdir } from "os";
import { join } from "path";
import { spawnSync } from "child_process";
const gbrainPath = spawnSync("which", ["gbrain"], { encoding: "utf-8" }).stdout.trim();
const gbrainPath = spawnSync("which", ["gbrain"], { encoding: "utf-8", timeout: 30_000 }).stdout.trim();
const gbrainAvailable = gbrainPath.length > 0;
const voyageKey = process.env.VOYAGE_API_KEY?.trim() ?? "";
const voyageKeyPresent = voyageKey.length > 0;
@@ -114,14 +114,14 @@ The queue module is a simple FIFO job queue.
);
// Make it a git repo because gbrain's code-sync strategy expects one.
const gitInit = spawnSync("git", ["init", "-q"], { cwd: fixtureDir, encoding: "utf-8" });
const gitInit = spawnSync("git", ["init", "-q"], { cwd: fixtureDir, encoding: "utf-8", timeout: 30_000 });
if (gitInit.status !== 0) {
throw new Error(`git init failed: ${gitInit.stderr}`);
}
spawnSync("git", ["config", "user.email", "test@example.invalid"], { cwd: fixtureDir });
spawnSync("git", ["config", "user.name", "test"], { cwd: fixtureDir });
spawnSync("git", ["add", "."], { cwd: fixtureDir });
spawnSync("git", ["commit", "-q", "-m", "fixture"], { cwd: fixtureDir });
spawnSync("git", ["config", "user.email", "test@example.invalid"], { cwd: fixtureDir, timeout: 30_000 });
spawnSync("git", ["config", "user.name", "test"], { cwd: fixtureDir, timeout: 30_000 });
spawnSync("git", ["add", "."], { cwd: fixtureDir, timeout: 30_000 });
spawnSync("git", ["commit", "-q", "-m", "fixture"], { cwd: fixtureDir, timeout: 30_000 });
return {
root,
+1 -1
View File
@@ -29,7 +29,7 @@ const ROOT = path.resolve(import.meta.dir, '..');
const GEMINI_AVAILABLE = (() => {
try {
const result = Bun.spawnSync(['which', 'gemini']);
const result = Bun.spawnSync(['which', 'gemini'], { timeout: 30_000 });
return result.exitCode === 0;
} catch { return false; }
})();
+1 -1
View File
@@ -39,7 +39,7 @@ describe('gen-skill-docs import purity', () => {
}
console.log('IMPORT_PURE');
`;
const out = Bun.spawnSync(['bun', '-e', probe], { cwd: ROOT });
const out = Bun.spawnSync(['bun', '-e', probe], { cwd: ROOT, timeout: 120_000 });
const stdout = out.stdout.toString();
const stderr = out.stderr.toString();
expect(stderr, stderr).not.toContain('import mutated');
+1 -1
View File
@@ -21,7 +21,7 @@ describe('gen-skill-docs --out-dir (B2 render isolation)', () => {
}
function porcelain(): string {
const r = spawnSync('git', ['status', '--porcelain'], { cwd: ROOT, encoding: 'utf-8' });
const r = spawnSync('git', ['status', '--porcelain'], { cwd: ROOT, encoding: 'utf-8', timeout: 30_000 });
return r.status === 0 ? r.stdout : '';
}
+12 -7
View File
@@ -139,7 +139,7 @@ const EXTERNAL_OUT = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-gen-docs-out-
{
const render = Bun.spawnSync(
['bun', 'run', 'scripts/gen-skill-docs.ts', '--host', 'all', '--out-dir', EXTERNAL_OUT],
{ cwd: ROOT, stdout: 'pipe', stderr: 'pipe' },
{ cwd: ROOT, stdout: 'pipe', stderr: 'pipe', timeout: 120_000 },
);
if (render.exitCode !== 0) {
throw new Error(
@@ -315,6 +315,7 @@ describe('gen-skill-docs', () => {
cwd: ROOT,
stdout: 'pipe',
stderr: 'pipe',
timeout: 120_000,
});
expect(result.exitCode).toBe(0);
const output = result.stdout.toString();
@@ -1965,7 +1966,7 @@ describe('Codex generation (--host codex)', () => {
'/tmp/gstack-claude-error-XXXXXX',
'/tmp/gstack-claude-diff-XXXXXX',
]) {
const result = spawnSync('mktemp', [template], { encoding: 'utf-8' });
const result = spawnSync('mktemp', [template], { encoding: 'utf-8', timeout: 30_000 });
expect(result.status).toBe(0);
const created = result.stdout.trim();
expect(created.startsWith(template.replace('XXXXXX', ''))).toBe(true);
@@ -1990,6 +1991,7 @@ describe('Codex generation (--host codex)', () => {
cwd: ROOT,
stdout: 'pipe',
stderr: 'pipe',
timeout: 120_000,
});
expect(result.exitCode).toBe(0);
const output = result.stdout.toString();
@@ -2005,11 +2007,13 @@ describe('Codex generation (--host codex)', () => {
cwd: ROOT,
stdout: 'pipe',
stderr: 'pipe',
timeout: 120_000,
});
const agentsResult = Bun.spawnSync(['bun', 'run', 'scripts/gen-skill-docs.ts', '--host', 'agents', '--dry-run', '--out-dir', EXTERNAL_OUT], {
cwd: ROOT,
stdout: 'pipe',
stderr: 'pipe',
timeout: 120_000,
});
expect(codexResult.exitCode).toBe(0);
expect(agentsResult.exitCode).toBe(0);
@@ -2224,6 +2228,7 @@ describe('Codex generation (--host codex)', () => {
cwd: ROOT,
stdout: 'pipe',
stderr: 'pipe',
timeout: 120_000,
});
expect(override.exitCode).toBe(0);
const content = fs.readFileSync(path.join(overrideOut, '.agents', 'skills', 'gstack-ship', 'SKILL.md'), 'utf-8');
@@ -2342,10 +2347,10 @@ describe('Factory generation (--host factory)', () => {
test('--host droid alias works', () => {
const factoryResult = Bun.spawnSync(['bun', 'run', 'scripts/gen-skill-docs.ts', '--host', 'factory', '--dry-run', '--out-dir', EXTERNAL_OUT], {
cwd: ROOT, stdout: 'pipe', stderr: 'pipe',
cwd: ROOT, stdout: 'pipe', stderr: 'pipe', timeout: 120_000,
});
const droidResult = Bun.spawnSync(['bun', 'run', 'scripts/gen-skill-docs.ts', '--host', 'droid', '--dry-run', '--out-dir', EXTERNAL_OUT], {
cwd: ROOT, stdout: 'pipe', stderr: 'pipe',
cwd: ROOT, stdout: 'pipe', stderr: 'pipe', timeout: 120_000,
});
expect(factoryResult.exitCode).toBe(0);
expect(droidResult.exitCode).toBe(0);
@@ -2354,7 +2359,7 @@ describe('Factory generation (--host factory)', () => {
test('--host factory --dry-run freshness', () => {
const result = Bun.spawnSync(['bun', 'run', 'scripts/gen-skill-docs.ts', '--host', 'factory', '--dry-run', '--out-dir', EXTERNAL_OUT], {
cwd: ROOT, stdout: 'pipe', stderr: 'pipe',
cwd: ROOT, stdout: 'pipe', stderr: 'pipe', timeout: 120_000,
});
expect(result.exitCode).toBe(0);
const output = result.stdout.toString();
@@ -2432,7 +2437,7 @@ describe('Parameterized host smoke tests', () => {
test('--dry-run freshness check passes', () => {
const result = Bun.spawnSync(
['bun', 'run', 'scripts/gen-skill-docs.ts', '--host', hostConfig.name, '--dry-run', '--out-dir', EXTERNAL_OUT],
{ cwd: ROOT, stdout: 'pipe', stderr: 'pipe' }
{ cwd: ROOT, stdout: 'pipe', stderr: 'pipe', timeout: 120_000 }
);
expect(result.exitCode).toBe(0);
const output = result.stdout.toString();
@@ -2457,7 +2462,7 @@ describe('--host all', () => {
// claude host plus every external host regenerate deterministically.
test('--host all generates for all registered hosts', () => {
const result = Bun.spawnSync(['bun', 'run', 'scripts/gen-skill-docs.ts', '--host', 'all', '--dry-run', '--out-dir', EXTERNAL_OUT], {
cwd: ROOT, stdout: 'pipe', stderr: 'pipe',
cwd: ROOT, stdout: 'pipe', stderr: 'pipe', timeout: 120_000,
});
expect(result.exitCode).toBe(0);
const output = result.stdout.toString();
+2 -1
View File
@@ -180,10 +180,11 @@ describe("gstack-global-discover", () => {
// Create a git repo as the session target
const repoDir = join(tmpDir, "fake-repo");
mkdirSync(repoDir);
spawnSync("git", ["init"], { cwd: repoDir, stdio: "pipe" });
spawnSync("git", ["init"], { cwd: repoDir, stdio: "pipe", timeout: 30_000 });
spawnSync("git", ["commit", "--allow-empty", "-m", "init"], {
cwd: repoDir,
stdio: "pipe",
timeout: 30_000,
});
// Write a session with a 20KB first line (simulates Codex v0.117+)
+9 -8
View File
@@ -119,7 +119,7 @@ exit 0
* test focused on artifacts-init's branching logic, not git plumbing.
*/
function makeFakeGit() {
const realGit = spawnSync('which', ['git'], { encoding: 'utf-8' }).stdout.trim();
const realGit = spawnSync('which', ['git'], { encoding: 'utf-8', timeout: 30_000 }).stdout.trim();
const script = `#!/bin/bash
# Walk argv past leading -C <dir> and similar flags to find the real subcommand.
args=("$@")
@@ -157,6 +157,7 @@ function run(argv: string[], opts: { env?: Record<string, string>; input?: strin
encoding: 'utf-8',
input: opts.input,
cwd: ROOT,
timeout: 30_000,
});
return {
stdout: res.stdout || '',
@@ -176,7 +177,7 @@ beforeEach(() => {
fakeBinDir = fs.mkdtempSync(path.join(os.tmpdir(), 'artifacts-fake-bin-'));
ghCallLog = path.join(fakeBinDir, 'gh-calls.log');
glabCallLog = path.join(fakeBinDir, 'glab-calls.log');
spawnSync('git', ['init', '--bare', '-q', '-b', 'main', bareRemote]);
spawnSync('git', ['init', '--bare', '-q', '-b', 'main', bareRemote], { timeout: 30_000 });
makeFakeGit();
});
@@ -277,7 +278,7 @@ describe('gstack-artifacts-init canonical URL storage (codex Finding #10)', () =
makeFakeGh({ webUrl: 'https://github.com/testuser/gstack-artifacts-testuser' });
const r = run(['--host', 'github']);
expect(r.status).toBe(0);
const remote = spawnSync('git', ['-C', tmpHome, 'remote', 'get-url', 'origin'], { encoding: 'utf-8' });
const remote = spawnSync('git', ['-C', tmpHome, 'remote', 'get-url', 'origin'], { encoding: 'utf-8', timeout: 30_000 });
expect(remote.stdout.trim()).toBe('https://github.com/testuser/gstack-artifacts-testuser');
});
@@ -288,7 +289,7 @@ describe('gstack-artifacts-init canonical URL storage (codex Finding #10)', () =
});
const r = run(['--host', 'github']);
expect(r.status).toBe(0);
const remote = spawnSync('git', ['-C', tmpHome, 'remote', 'get-url', 'origin'], { encoding: 'utf-8' });
const remote = spawnSync('git', ['-C', tmpHome, 'remote', 'get-url', 'origin'], { encoding: 'utf-8', timeout: 30_000 });
expect(remote.stdout.trim()).toBe('git@github.com:testuser/gstack-artifacts-testuser.git');
});
@@ -296,7 +297,7 @@ describe('gstack-artifacts-init canonical URL storage (codex Finding #10)', () =
makeFakeGh({ gitProtocol: 'unset' });
const r = run(['--host', 'github']);
expect(r.status).toBe(0);
const remote = spawnSync('git', ['-C', tmpHome, 'remote', 'get-url', 'origin'], { encoding: 'utf-8' });
const remote = spawnSync('git', ['-C', tmpHome, 'remote', 'get-url', 'origin'], { encoding: 'utf-8', timeout: 30_000 });
expect(remote.stdout.trim()).toBe('https://github.com/testuser/gstack-artifacts-testuser');
});
@@ -304,7 +305,7 @@ describe('gstack-artifacts-init canonical URL storage (codex Finding #10)', () =
makeFakeGlab({ gitProtocol: 'ssh' });
const r = run(['--host', 'gitlab']);
expect(r.status).toBe(0);
const remote = spawnSync('git', ['-C', tmpHome, 'remote', 'get-url', 'origin'], { encoding: 'utf-8' });
const remote = spawnSync('git', ['-C', tmpHome, 'remote', 'get-url', 'origin'], { encoding: 'utf-8', timeout: 30_000 });
expect(remote.stdout.trim()).toBe('git@gitlab.com:testuser/gstack-artifacts-testuser.git');
});
});
@@ -359,7 +360,7 @@ describe('gstack-artifacts-init idempotency', () => {
makeFakeGh({ gitProtocol: 'ssh' });
const r = run(['--remote', 'https://github.com/testuser/gstack-artifacts-testuser']);
expect(r.status).toBe(0);
const remote = spawnSync('git', ['-C', tmpHome, 'remote', 'get-url', 'origin'], { encoding: 'utf-8' });
const remote = spawnSync('git', ['-C', tmpHome, 'remote', 'get-url', 'origin'], { encoding: 'utf-8', timeout: 30_000 });
expect(remote.stdout.trim()).toBe('https://github.com/testuser/gstack-artifacts-testuser');
});
@@ -372,7 +373,7 @@ describe('gstack-artifacts-init idempotency', () => {
'ssh',
]);
expect(r.status).toBe(0);
const remote = spawnSync('git', ['-C', tmpHome, 'remote', 'get-url', 'origin'], { encoding: 'utf-8' });
const remote = spawnSync('git', ['-C', tmpHome, 'remote', 'get-url', 'origin'], { encoding: 'utf-8', timeout: 30_000 });
expect(remote.stdout.trim()).toBe('git@github.com:testuser/gstack-artifacts-testuser.git');
});
+1 -1
View File
@@ -13,7 +13,7 @@ const ROOT = path.resolve(import.meta.dir, '..');
const URL_BIN = path.join(ROOT, 'bin', 'gstack-artifacts-url');
function run(args: string[]): { code: number; stdout: string; stderr: string } {
const r = spawnSync(URL_BIN, args, { encoding: 'utf-8' });
const r = spawnSync(URL_BIN, args, { encoding: 'utf-8', timeout: 30_000 });
return {
code: r.status ?? -1,
stdout: (r.stdout || '').trim(),
+2 -2
View File
@@ -68,7 +68,7 @@ function runImport(sessionPath: string): { stdout: string; stderr: string; statu
env.GSTACK_STATE_ROOT = stateRoot;
env.GSTACK_QUESTION_LOG_NO_DERIVE = '1';
delete env.GSTACK_HOME;
const res = spawnSync(BIN, [sessionPath], { env, encoding: 'utf-8', cwd: ROOT });
const res = spawnSync(BIN, [sessionPath], { env, encoding: 'utf-8', cwd: ROOT, timeout: 30_000 });
return {
stdout: res.stdout ?? '',
stderr: res.stderr ?? '',
@@ -196,7 +196,7 @@ describe('default mode (no args → latest)', () => {
}
env.GSTACK_STATE_ROOT = stateRoot;
env.CODEX_SESSIONS_ROOT = emptyDir;
const res = spawnSync(BIN, [], { env, encoding: 'utf-8', cwd: ROOT });
const res = spawnSync(BIN, [], { env, encoding: 'utf-8', cwd: ROOT, timeout: 30_000 });
expect(res.status).toBe(0);
expect(res.stdout).toMatch(/NO_SESSIONS/);
} finally {
+1
View File
@@ -45,6 +45,7 @@ const STATE = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-config-test-'));
function get(key: string): { out: string; code: number } {
const r = spawnSync('bash', [CONFIG_BIN, 'get', key], {
encoding: 'utf-8',
timeout: 30_000,
env: { ...process.env, GSTACK_STATE_ROOT: STATE },
});
return { out: r.stdout ?? '', code: r.status ?? -1 };
+1
View File
@@ -28,6 +28,7 @@ function run(args: string[]) {
// live tree (observed in the free-tests CI job). Relink behavior itself is
// covered in isolation by test/relink.test.ts's mock install.
env: { ...process.env, GSTACK_STATE_ROOT: stateRoot, GSTACK_SETUP_RUNNING: "1" },
timeout: 30_000,
});
return {
+1
View File
@@ -16,6 +16,7 @@ function cfg(args: string[]): { code: number; out: string; err: string } {
const r = spawnSync(CONFIG, args, {
encoding: "utf8",
env: { ...process.env, GSTACK_HOME: home },
timeout: 30_000,
});
return { code: r.status ?? 0, out: r.stdout ?? "", err: r.stderr ?? "" };
}
+3 -3
View File
@@ -20,18 +20,18 @@ function opts(): ExecSyncOptionsWithStringEncoding {
}
function log(arg: string, expectFail = false): { out: string; code: number } {
try {
return { out: execSync(`${LOG} '${arg.replace(/'/g, "'\\''")}'`, opts()).trim(), code: 0 };
return { out: execSync(`${LOG} '${arg.replace(/'/g, "'\\''")}'`, opts()).trim(), code: 0 }; // timeout via opts()
} catch (e: any) {
if (expectFail) return { out: (e.stderr?.toString() || "").trim(), code: e.status || 1 };
throw e;
}
}
function logFlag(flag: string): string {
return execSync(`${LOG} ${flag}`, opts()).trim();
return execSync(`${LOG} ${flag}`, opts()).trim(); // timeout via opts()
}
function search(args = ""): string {
try {
return execSync(`${SEARCH} ${args}`, opts()).trim();
return execSync(`${SEARCH} ${args}`, opts()).trim(); // timeout via opts()
} catch {
return "";
}
+3 -3
View File
@@ -15,13 +15,13 @@ const ROOT = path.resolve(import.meta.dir, '..');
const DETACH = path.join(ROOT, 'bin', 'gstack-detach');
function ownPgid(): string {
return (spawnSync('ps', ['-o', 'pgid=', '-p', String(process.pid)], { encoding: 'utf-8' }).stdout || '').trim();
return (spawnSync('ps', ['-o', 'pgid=', '-p', String(process.pid)], { encoding: 'utf-8', timeout: 30_000 }).stdout || '').trim();
}
function waitFor(pred: () => boolean, ms: number): boolean {
const end = Date.now() + ms;
while (Date.now() < end) {
if (pred()) return true;
spawnSync('sleep', ['0.2']);
spawnSync('sleep', ['0.2'], { timeout: 30_000 });
}
return pred();
}
@@ -90,7 +90,7 @@ describe('gstack-detach', () => {
}, 20000);
test('rejects missing command (exit 2)', () => {
const r = spawnSync(DETACH, ['--label', 'x'], { encoding: 'utf-8' });
const r = spawnSync(DETACH, ['--label', 'x'], { encoding: 'utf-8', timeout: 30_000 });
expect(r.status).toBe(2);
});
});
+2
View File
@@ -36,6 +36,7 @@ function runDev(...args: string[]): { stdout: string; stderr: string; status: nu
env: { ...process.env, GSTACK_HOME: tmpHome },
encoding: 'utf-8',
cwd: ROOT,
timeout: 30_000,
});
return {
stdout: res.stdout ?? '',
@@ -49,6 +50,7 @@ function logQuestion(payload: Record<string, unknown>): number {
env: { ...process.env, GSTACK_HOME: tmpHome },
encoding: 'utf-8',
cwd: ROOT,
timeout: 30_000,
});
return res.status ?? -1;
}
+3
View File
@@ -36,6 +36,7 @@ function run(args: string[]) {
const result = spawnSync(BIN, args, {
encoding: 'utf-8',
env: { ...process.env, GSTACK_HOME: home },
timeout: 30_000,
});
return { code: result.status ?? -1, stdout: result.stdout || '', stderr: result.stderr || '' };
}
@@ -122,11 +123,13 @@ describe('gstack-egress grants', () => {
const config = spawnSync(path.join(ROOT, 'bin', 'gstack-config'), ['set', 'telemetry', 'community'], {
encoding: 'utf-8',
env: { ...process.env, GSTACK_HOME: home },
timeout: 30_000,
});
expect(config.status).toBe(0);
spawnSync(path.join(ROOT, 'bin', 'gstack-config'), ['set', 'artifacts_sync_mode', 'full'], {
encoding: 'utf-8',
env: { ...process.env, GSTACK_HOME: home },
timeout: 30_000,
});
const r = run(['grants', '--json']);
expect(r.code).toBe(0);
@@ -66,6 +66,7 @@ function runDetect(extraEnv: Record<string, string> = {}): { code: number; json:
...extraEnv,
},
encoding: 'utf-8',
timeout: 30_000,
});
let json: any = null;
try {
+3
View File
@@ -94,6 +94,7 @@ function runVerify(token: string, url: string): { code: number; stdout: string;
GSTACK_HOME: tmpDir,
},
encoding: 'utf-8',
timeout: 30_000,
});
return {
code: result.status ?? -1,
@@ -249,6 +250,7 @@ describe('gstack-gbrain-mcp-verify', () => {
const r = spawnSync(VERIFY_BIN, ['https://example.com/mcp'], {
env: { ...process.env, PATH: `${fakeBinDir}:${process.env.PATH}`, GBRAIN_MCP_TOKEN: '' },
encoding: 'utf-8',
timeout: 30_000,
});
expect(r.status).toBe(2);
expect(r.stderr).toContain('GBRAIN_MCP_TOKEN');
@@ -259,6 +261,7 @@ describe('gstack-gbrain-mcp-verify', () => {
const r = spawnSync(VERIFY_BIN, [], {
env: { ...process.env, PATH: `${fakeBinDir}:${process.env.PATH}`, GBRAIN_MCP_TOKEN: 'x' },
encoding: 'utf-8',
timeout: 30_000,
});
expect(r.status).toBe(2);
});
+13 -8
View File
@@ -145,6 +145,7 @@ function run(
env,
encoding: 'utf-8',
cwd: ROOT,
timeout: 30_000,
});
}
@@ -163,13 +164,13 @@ function gbrainCalls(): string[] {
function setupGstackRepo(remoteUrl: string) {
// Real git repo at gstackHome with at least one commit + an origin remote.
fs.mkdirSync(gstackHome, { recursive: true });
spawnSync('git', ['-C', gstackHome, 'init', '-q', '-b', 'main'], { stdio: 'pipe' });
spawnSync('git', ['-C', gstackHome, 'config', 'user.email', 'test@example.com'], { stdio: 'pipe' });
spawnSync('git', ['-C', gstackHome, 'config', 'user.name', 'test'], { stdio: 'pipe' });
spawnSync('git', ['-C', gstackHome, 'init', '-q', '-b', 'main'], { stdio: 'pipe', timeout: 30_000 });
spawnSync('git', ['-C', gstackHome, 'config', 'user.email', 'test@example.com'], { stdio: 'pipe', timeout: 30_000 });
spawnSync('git', ['-C', gstackHome, 'config', 'user.name', 'test'], { stdio: 'pipe', timeout: 30_000 });
fs.writeFileSync(path.join(gstackHome, '.brain-allowlist'), '# allowlist\n');
spawnSync('git', ['-C', gstackHome, 'add', '.'], { stdio: 'pipe' });
spawnSync('git', ['-C', gstackHome, 'commit', '-q', '-m', 'init'], { stdio: 'pipe' });
spawnSync('git', ['-C', gstackHome, 'remote', 'add', 'origin', remoteUrl], { stdio: 'pipe' });
spawnSync('git', ['-C', gstackHome, 'add', '.'], { stdio: 'pipe', timeout: 30_000 });
spawnSync('git', ['-C', gstackHome, 'commit', '-q', '-m', 'init'], { stdio: 'pipe', timeout: 30_000 });
spawnSync('git', ['-C', gstackHome, 'remote', 'add', 'origin', remoteUrl], { stdio: 'pipe', timeout: 30_000 });
}
beforeEach(() => {
@@ -316,6 +317,7 @@ describe('gstack-gbrain-source-wireup — wireup mode', () => {
const check = spawnSync('bash', ['-c', `command -v gbrain && gbrain --version`], {
env: { PATH: `${hostLikeDir}:${process.env.PATH || '/usr/bin:/bin'}` },
encoding: 'utf-8',
timeout: 30_000,
});
expect(check.status).toBe(0);
expect(check.stdout).toContain('gbrain 0.18.2');
@@ -526,13 +528,15 @@ describe('gstack-gbrain-source-wireup — defensive paths', () => {
run([], { env: { GSTACK_BRAIN_NO_SYNC: '1' } });
// Make a new commit on parent so worktree HEAD is "behind"
fs.writeFileSync(path.join(gstackHome, 'newfile.md'), 'new');
spawnSync('git', ['-C', gstackHome, 'add', '.'], { stdio: 'pipe' });
spawnSync('git', ['-C', gstackHome, 'commit', '-q', '-m', 'second commit'], { stdio: 'pipe' });
spawnSync('git', ['-C', gstackHome, 'add', '.'], { stdio: 'pipe', timeout: 30_000 });
spawnSync('git', ['-C', gstackHome, 'commit', '-q', '-m', 'second commit'], { stdio: 'pipe', timeout: 30_000 });
const parentHeadAfter = spawnSync('git', ['-C', gstackHome, 'rev-parse', 'HEAD'], {
encoding: 'utf-8',
timeout: 30_000,
}).stdout.trim();
const worktreeHeadBefore = spawnSync('git', ['-C', worktreeDir, 'rev-parse', 'HEAD'], {
encoding: 'utf-8',
timeout: 30_000,
}).stdout.trim();
expect(parentHeadAfter).not.toBe(worktreeHeadBefore); // sanity: parent advanced
// --no-pull should leave worktree HEAD where it was
@@ -540,6 +544,7 @@ describe('gstack-gbrain-source-wireup — defensive paths', () => {
expect(r.status).toBe(0);
const worktreeHeadAfter = spawnSync('git', ['-C', worktreeDir, 'rev-parse', 'HEAD'], {
encoding: 'utf-8',
timeout: 30_000,
}).stdout.trim();
expect(worktreeHeadAfter).toBe(worktreeHeadBefore);
expect(worktreeHeadAfter).not.toBe(parentHeadAfter);
+26 -26
View File
@@ -137,7 +137,7 @@ describe("gstack-gbrain-sync CLI", () => {
const repo = mkdtempSync(join(tmpdir(), "gstack-pinned-source-repo-"));
const commandLog = join(home, "gbrain-commands.log");
mkdirSync(gstackHome, { recursive: true });
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo });
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo, timeout: 30_000 });
writeFileSync(join(repo, ".gbrain-source"), "client-acme-app\n");
writeFileSync(join(bindir, "gbrain"), `#!/bin/sh
printf '%s\\n' "$*" >> "$GSTACK_TEST_GBRAIN_LOG"
@@ -179,7 +179,7 @@ exit 99
mkdirSync(gstackHome, { recursive: true });
mkdirSync(join(home, ".gbrain"), { recursive: true });
writeFileSync(join(home, ".gbrain", "config.json"), JSON.stringify({ engine: "pglite", database_url: "pglite:///test" }));
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo });
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo, timeout: 30_000 });
writeFileSync(join(repo, ".gbrain-source"), "client-acme-app\n");
symlinkSync(repo, link, "dir");
writeFileSync(join(bindir, "gbrain"), `#!/bin/sh
@@ -223,7 +223,7 @@ esac
const bindir = mkdtempSync(join(tmpdir(), "gstack-pinned-dream-bin-"));
const repo = mkdtempSync(join(tmpdir(), "gstack-pinned-dream-repo-"));
mkdirSync(gstackHome, { recursive: true });
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo });
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo, timeout: 30_000 });
writeFileSync(join(repo, ".gbrain-source"), "client-acme-app\n");
writeFileSync(join(bindir, "gbrain"), "#!/bin/sh\nexit 99\n");
chmodSync(join(bindir, "gbrain"), 0o755);
@@ -247,7 +247,7 @@ esac
const gstackHome = join(home, ".gstack");
const repo = mkdtempSync(join(tmpdir(), "gstack-unreadable-pin-repo-"));
mkdirSync(gstackHome, { recursive: true });
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo });
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo, timeout: 30_000 });
mkdirSync(join(repo, ".gbrain-source"));
const r = spawnSync("bun", [SCRIPT, "--dry-run", "--code-only", "--quiet"], {
@@ -282,8 +282,8 @@ esac
const gstackHome = join(home, ".gstack");
mkdirSync(gstackHome, { recursive: true });
const repo = mkdtempSync(join(tmpdir(), "gstack-source-id-repo-"));
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo });
spawnSync("git", ["remote", "add", "origin", remote], { cwd: repo });
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo, timeout: 30_000 });
spawnSync("git", ["remote", "add", "origin", remote], { cwd: repo, timeout: 30_000 });
const r = spawnSync("bun", [SCRIPT, "--dry-run", "--code-only", "--quiet"], {
encoding: "utf-8",
@@ -312,7 +312,7 @@ esac
const gstackHome = join(home, ".gstack");
mkdirSync(gstackHome, { recursive: true });
const repo = mkdtempSync(join(tmpdir(), "gstack-no-origin-"));
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo });
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo, timeout: 30_000 });
// No `git remote add origin` — this is the no-remote case.
const r = spawnSync("bun", [SCRIPT, "--dry-run", "--code-only", "--quiet"], {
@@ -347,7 +347,7 @@ esac
const parent = mkdtempSync(join(tmpdir(), "gstack-empty-base-"));
const repo = join(parent, "___");
mkdirSync(repo);
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo });
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo, timeout: 30_000 });
// No `origin` remote — forces the basename-fallback path.
const r = spawnSync("bun", [SCRIPT, "--dry-run", "--code-only", "--quiet"], {
@@ -381,8 +381,8 @@ esac
const gstackHome = join(home, ".gstack");
mkdirSync(gstackHome, { recursive: true });
const repo = mkdtempSync(join(tmpdir(), "gstack-host-collide-"));
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo });
spawnSync("git", ["remote", "add", "origin", "https://github.com/example/multihost.git"], { cwd: repo });
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo, timeout: 30_000 });
spawnSync("git", ["remote", "add", "origin", "https://github.com/example/multihost.git"], { cwd: repo, timeout: 30_000 });
// Dry-run still gates the code stage on `command -v gbrain`. Drop a no-op
// shim on PATH so the stage runs (we only assert the preview line, never
@@ -569,8 +569,8 @@ esac
const repoA = mkdtempSync(join(tmpdir(), "gstack-worktree-a-"));
const repoB = mkdtempSync(join(tmpdir(), "gstack-worktree-b-"));
for (const repo of [repoA, repoB]) {
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo });
spawnSync("git", ["remote", "add", "origin", remote], { cwd: repo });
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo, timeout: 30_000 });
spawnSync("git", ["remote", "add", "origin", remote], { cwd: repo, timeout: 30_000 });
}
const idOf = (cwd: string): string => {
@@ -606,8 +606,8 @@ esac
const gstackHome = join(home, ".gstack");
mkdirSync(gstackHome, { recursive: true });
const repo = mkdtempSync(join(tmpdir(), "gstack-worktree-stable-"));
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo });
spawnSync("git", ["remote", "add", "origin", remote], { cwd: repo });
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo, timeout: 30_000 });
spawnSync("git", ["remote", "add", "origin", remote], { cwd: repo, timeout: 30_000 });
const idOf = (): string => {
const r = spawnSync("bun", [SCRIPT, "--dry-run", "--code-only", "--quiet"], {
@@ -635,8 +635,8 @@ esac
const gstackHome = join(home, ".gstack");
mkdirSync(gstackHome, { recursive: true });
const repo = mkdtempSync(join(tmpdir(), "gstack-legacy-cleanup-"));
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo });
spawnSync("git", ["remote", "add", "origin", "https://github.com/garrytan/gstack.git"], { cwd: repo });
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo, timeout: 30_000 });
spawnSync("git", ["remote", "add", "origin", "https://github.com/garrytan/gstack.git"], { cwd: repo, timeout: 30_000 });
const r = spawnSync("bun", [SCRIPT, "--dry-run", "--code-only", "--quiet"], {
encoding: "utf-8",
@@ -671,8 +671,8 @@ esac
const gstackHome = join(home, ".gstack");
mkdirSync(gstackHome, { recursive: true });
const repo = mkdtempSync(join(tmpdir(), "gstack-attach-preview-"));
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo });
spawnSync("git", ["remote", "add", "origin", "https://github.com/garrytan/gstack.git"], { cwd: repo });
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo, timeout: 30_000 });
spawnSync("git", ["remote", "add", "origin", "https://github.com/garrytan/gstack.git"], { cwd: repo, timeout: 30_000 });
const r = spawnSync("bun", [SCRIPT, "--dry-run", "--code-only", "--quiet"], {
encoding: "utf-8",
@@ -727,8 +727,8 @@ describe("derivePathOnlyHashLegacyId", () => {
// legacy id regardless of $GSTACK_HOSTNAME, because the pre-#1468 hash
// didn't include hostname.
const repo = mkdtempSync(join(tmpdir(), "gstack-legacy-id-"));
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo });
spawnSync("git", ["remote", "add", "origin", "https://github.com/example/legacy-test.git"], { cwd: repo });
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo, timeout: 30_000 });
spawnSync("git", ["remote", "add", "origin", "https://github.com/example/legacy-test.git"], { cwd: repo, timeout: 30_000 });
const cwd = process.cwd();
try {
@@ -754,8 +754,8 @@ describe("derivePathOnlyHashLegacyId", () => {
// host-fold id must differ for any non-empty hostname, so the migration
// can detect + clean up the orphan.
const repo = mkdtempSync(join(tmpdir(), "gstack-legacy-id-distinct-"));
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo });
spawnSync("git", ["remote", "add", "origin", "https://github.com/example/distinct.git"], { cwd: repo });
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo, timeout: 30_000 });
spawnSync("git", ["remote", "add", "origin", "https://github.com/example/distinct.git"], { cwd: repo, timeout: 30_000 });
const cwd = process.cwd();
try {
@@ -890,10 +890,10 @@ describe("constrainSourceId truncation (hyphen-boundary cut)", () => {
const gstackHome = join(home, ".gstack");
mkdirSync(gstackHome, { recursive: true });
const repo = mkdtempSync(join(tmpdir(), "gstack-hyphen-cut-"));
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo });
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo, timeout: 30_000 });
// Remote chosen to be long enough that constrainSourceId truncates and
// the boundary lands inside the word `skill`.
spawnSync("git", ["remote", "add", "origin", "https://github.com/drummerms-av-sow-wiz/skill-270c0001.git"], { cwd: repo });
spawnSync("git", ["remote", "add", "origin", "https://github.com/drummerms-av-sow-wiz/skill-270c0001.git"], { cwd: repo, timeout: 30_000 });
const r = spawnSync("bun", [SCRIPT, "--dry-run", "--code-only", "--quiet"], {
encoding: "utf-8",
@@ -924,8 +924,8 @@ describe("constrainSourceId truncation (hyphen-boundary cut)", () => {
const gstackHome = join(home, ".gstack");
mkdirSync(gstackHome, { recursive: true });
const repo = mkdtempSync(join(tmpdir(), "gstack-https-period-"));
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo });
spawnSync("git", ["remote", "add", "origin", "https://github.com/foo/bar.git"], { cwd: repo });
spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo, timeout: 30_000 });
spawnSync("git", ["remote", "add", "origin", "https://github.com/foo/bar.git"], { cwd: repo, timeout: 30_000 });
const r = spawnSync("bun", [SCRIPT, "--dry-run", "--code-only", "--quiet"], {
encoding: "utf-8",
+1 -1
View File
@@ -26,7 +26,7 @@ const ROOT = path.resolve(__dirname, '..');
function trackedTestFiles(): string[] {
const out = spawnSync('git', ['ls-files', '*.test.ts'], {
cwd: ROOT, encoding: 'utf-8',
cwd: ROOT, encoding: 'utf-8', timeout: 30_000,
});
if (out.status !== 0) throw new Error(`git ls-files failed: ${out.stderr}`);
return out.stdout.split('\n').filter(Boolean);
+10 -6
View File
@@ -500,6 +500,7 @@ describe("gstack-memory-ingest writer (gbrain v0.20+ batch `import` interface)",
const POLICY = join(import.meta.dir, "..", "bin", "gstack-gbrain-repo-policy");
const seeded = spawnSync("bash", [POLICY, "set", "_unattributed", "deny"], {
encoding: "utf-8",
timeout: 30_000,
env: { ...process.env, HOME: home, GSTACK_HOME: gstackHome },
});
expect(seeded.status).toBe(0);
@@ -622,6 +623,7 @@ esac
expect(existsSync(stagingCopy)).toBe(true);
const findMd = spawnSync("find", [stagingCopy, "-name", "*.md", "-type", "f"], {
encoding: "utf-8",
timeout: 30_000,
});
const mdPaths = (findMd.stdout || "").trim().split("\n").filter(Boolean);
expect(mdPaths.length).toBeGreaterThan(0);
@@ -685,6 +687,7 @@ esac
// walk to find a .md and read its head.)
const findMd = spawnSync("find", [stagingCopy, "-name", "*.md", "-type", "f"], {
encoding: "utf-8",
timeout: 30_000,
});
const mdPaths = (findMd.stdout || "").trim().split("\n").filter(Boolean);
expect(mdPaths.length).toBeGreaterThan(0);
@@ -913,8 +916,8 @@ describe("#2394: probe applies the same attribution gate as prepare", () => {
function makeAttributableCwd(home: string): string {
const repo = join(home, "work", "attributable-repo");
mkdirSync(repo, { recursive: true });
spawnSync("git", ["-C", repo, "init", "-q"], { encoding: "utf-8" });
spawnSync("git", ["-C", repo, "remote", "add", "origin", "https://github.com/foo/bar.git"], { encoding: "utf-8" });
spawnSync("git", ["-C", repo, "init", "-q"], { encoding: "utf-8", timeout: 30_000 });
spawnSync("git", ["-C", repo, "remote", "add", "origin", "https://github.com/foo/bar.git"], { encoding: "utf-8", timeout: 30_000 });
return repo;
}
@@ -993,8 +996,8 @@ describe("#2394: probe applies the same attribution gate as prepare", () => {
mkdirSync(gstackHome, { recursive: true });
const attributableCwd = join(home, "work", "attributable-repo");
mkdirSync(attributableCwd, { recursive: true });
spawnSync("git", ["-C", attributableCwd, "init", "-q"], { encoding: "utf-8" });
spawnSync("git", ["-C", attributableCwd, "remote", "add", "origin", "https://github.com/foo/bar.git"], { encoding: "utf-8" });
spawnSync("git", ["-C", attributableCwd, "init", "-q"], { encoding: "utf-8", timeout: 30_000 });
spawnSync("git", ["-C", attributableCwd, "remote", "add", "origin", "https://github.com/foo/bar.git"], { encoding: "utf-8", timeout: 30_000 });
const ts = new Date().toISOString();
const cwdLine = `{"type":"user","message":{"role":"user","content":"hello"},"timestamp":"${ts}","cwd":"${attributableCwd.replace(/\\/g, "\\\\")}"}\n`;
@@ -1077,8 +1080,8 @@ describe("#2392: transcript ingest honors per-remote trust policy", () => {
function makeRepoWithRemote(home: string, name: string, remoteUrl: string): string {
const repo = join(home, "work", name);
mkdirSync(repo, { recursive: true });
spawnSync("git", ["-C", repo, "init", "-q"], { encoding: "utf-8" });
spawnSync("git", ["-C", repo, "remote", "add", "origin", remoteUrl], { encoding: "utf-8" });
spawnSync("git", ["-C", repo, "init", "-q"], { encoding: "utf-8", timeout: 30_000 });
spawnSync("git", ["-C", repo, "remote", "add", "origin", remoteUrl], { encoding: "utf-8", timeout: 30_000 });
return repo;
}
@@ -1095,6 +1098,7 @@ describe("#2392: transcript ingest honors per-remote trust policy", () => {
function setPolicy(gstackHome: string, url: string, tier: string): void {
const r = spawnSync(POLICY_BIN, ["set", url, tier], {
encoding: "utf-8",
timeout: 30_000,
env: { ...process.env, GSTACK_HOME: gstackHome },
});
expect(r.status).toBe(0);
+16 -16
View File
@@ -326,7 +326,7 @@ describe("default-base detection (no --base)", () => {
function runWithoutBase(cwd: string): { exitCode: number; parsed: any } {
const proc = Bun.spawnSync(
["bun", "run", SCRIPT, "--bump", "patch", "--workspace-root", "null"],
{ cwd },
{ cwd, timeout: 30_000 },
);
const out = new TextDecoder().decode(proc.stdout);
return { exitCode: proc.exitCode, parsed: JSON.parse(out) };
@@ -403,11 +403,11 @@ describe("offline output contract (what /ship branches on, #2545)", () => {
// host:"unknown" on CI) while keeping ls-remote/fetch fully local.
const bare = join(root, "github.com", "origin.git");
mkdirSync(bare, { recursive: true });
Bun.spawnSync(["git", "init", "-q", "--bare", "-b", "main", bare]);
Bun.spawnSync(["git", "init", "-q", "--bare", "-b", "main", bare], { timeout: 30_000 });
const work = join(root, "work");
mkdirSync(work);
const git = (...args: string[]) =>
Bun.spawnSync(["git", "-c", "user.email=t@t", "-c", "user.name=t", ...args], { cwd: work });
Bun.spawnSync(["git", "-c", "user.email=t@t", "-c", "user.name=t", ...args], { cwd: work, timeout: 30_000 });
git("init", "-q", "-b", "main");
writeFileSync(join(work, "VERSION"), "1.0.0.0\n");
git("add", "-A");
@@ -427,7 +427,7 @@ describe("offline output contract (what /ship branches on, #2545)", () => {
const proc = Bun.spawnSync(
["bun", "run", NEXTVER, "--base", "main",
"--bump", "patch", "--current-version", "1.0.0.0", "--workspace-root", "null"],
{ cwd: work, env: { ...process.env, PATH: `${stubDir}:${process.env.PATH}` } },
{ cwd: work, env: { ...process.env, PATH: `${stubDir}:${process.env.PATH}` }, timeout: 30_000 },
);
rmSync(stubDir, { recursive: true, force: true });
rmSync(root, { recursive: true, force: true });
@@ -454,7 +454,7 @@ describe("offline output contract (what /ship branches on, #2545)", () => {
const proc = Bun.spawnSync(
["bun", "run", NEXTVER, "--base", "main",
"--bump", "patch", "--current-version", "1.0.0.0", "--workspace-root", "null"],
{ cwd: work, env: { ...process.env, PATH: `${stubDir}:${process.env.PATH}` } },
{ cwd: work, env: { ...process.env, PATH: `${stubDir}:${process.env.PATH}` }, timeout: 30_000 },
);
rmSync(stubDir, { recursive: true, force: true });
rmSync(root, { recursive: true, force: true });
@@ -475,7 +475,7 @@ describe("fetchGitClaimed (offline allocation — the anti-duplicate fallback, #
// (plus three earlier pairs found in the same audit). Git knows what the API
// was asked for, so offline now degrades the QUEUE VIEW, not the ALLOCATION.
function git(cwd: string, ...args: string[]) {
return Bun.spawnSync(["git", "-c", "user.email=t@t", "-c", "user.name=t", ...args], { cwd });
return Bun.spawnSync(["git", "-c", "user.email=t@t", "-c", "user.name=t", ...args], { cwd, timeout: 30_000 });
}
function fixture(): string {
@@ -600,7 +600,7 @@ describe("fetchGitClaimed — non-mutating live remote query (ls-remote first)",
// remote's LIVE branch list with zero local mutation — a path/file remote
// answers it offline, which is exactly what these fixtures use.
function git(cwd: string, ...args: string[]) {
return Bun.spawnSync(["git", "-c", "user.email=t@t", "-c", "user.name=t", ...args], { cwd });
return Bun.spawnSync(["git", "-c", "user.email=t@t", "-c", "user.name=t", ...args], { cwd, timeout: 30_000 });
}
// Local origin with: main (0.1.66.0), sibling (0.1.67.0, live claim), and
@@ -718,7 +718,7 @@ describe("fetchGitClaimed — unfetched live claims (G2: ls-remote advertises SH
// VERSION reads fail. The old `continue` silently dropped that LIVE claim —
// the exact duplicate-allocation this fallback exists to prevent.
function git(cwd: string, ...args: string[]) {
return Bun.spawnSync(["git", "-c", "user.email=t@t", "-c", "user.name=t", ...args], { cwd });
return Bun.spawnSync(["git", "-c", "user.email=t@t", "-c", "user.name=t", ...args], { cwd, timeout: 30_000 });
}
function cloneFixture(): { root: string; origin: string; clone: string } {
@@ -908,14 +908,14 @@ describe("width pinned on failed base read (3-digit repos)", () => {
// the base read fails too, which is the path under test.
writeFileSync(join(stubDir, "gh"), "#!/bin/sh\nexit 1\n", { mode: 0o755 });
writeFileSync(join(stubDir, "glab"), "#!/bin/sh\nexit 1\n", { mode: 0o755 });
Bun.spawnSync(["git", "init", "-q", "-b", "main"], { cwd: dir });
Bun.spawnSync(["git", "init", "-q", "-b", "main"], { cwd: dir, timeout: 30_000 });
writeFileSync(join(dir, "VERSION"), "0.99.2\n");
Bun.spawnSync(["git", "-c", "user.email=t@t", "-c", "user.name=t", "add", "-A"], { cwd: dir });
Bun.spawnSync(["git", "-c", "user.email=t@t", "-c", "user.name=t", "commit", "-qm", "init"], { cwd: dir });
Bun.spawnSync(["git", "-c", "user.email=t@t", "-c", "user.name=t", "add", "-A"], { cwd: dir, timeout: 30_000 });
Bun.spawnSync(["git", "-c", "user.email=t@t", "-c", "user.name=t", "commit", "-qm", "init"], { cwd: dir, timeout: 30_000 });
const proc = Bun.spawnSync(
["bun", "run", SCRIPT, "--base", "main", "--bump", "patch", "--workspace-root", "null"],
{ cwd: dir, env: { ...process.env, PATH: `${stubDir}:${process.env.PATH}` } },
{ cwd: dir, env: { ...process.env, PATH: `${stubDir}:${process.env.PATH}` }, timeout: 30_000 },
);
const out = JSON.parse(new TextDecoder().decode(proc.stdout));
// Zero base at the repo's OWN width — never "0.0.0.0" in a 3-digit repo.
@@ -946,7 +946,7 @@ describe("integration (smoke)", () => {
"1.6.3.0",
"--workspace-root",
"null", // skip sibling scan in CI
]);
], { timeout: 30_000 });
const out = new TextDecoder().decode(proc.stdout);
const parsed = JSON.parse(out);
expect(parsed).toHaveProperty("version");
@@ -976,7 +976,7 @@ describe("integration (smoke)", () => {
"null",
"--version-path",
"Tinas Second Brain/health-tracker/VERSION",
]);
], { timeout: 30_000 });
const out = new TextDecoder().decode(proc.stdout);
const parsed = JSON.parse(out);
expect(parsed).toHaveProperty("version_path", "Tinas Second Brain/health-tracker/VERSION");
@@ -1003,7 +1003,7 @@ describe("fetchGitClaimed — laundered ls-remote (exit 0, empty output) is neve
chmodSync(join(stubDir, "git"), 0o755);
const git = (cwd: string, ...args: string[]) =>
Bun.spawnSync(["git", "-c", "user.email=t@t", "-c", "user.name=t", ...args], { cwd });
Bun.spawnSync(["git", "-c", "user.email=t@t", "-c", "user.name=t", ...args], { cwd, timeout: 30_000 });
const cwd = process.cwd();
const oldPath = process.env.PATH;
@@ -1052,7 +1052,7 @@ describe("fetchGitClaimed — laundered ls-remote (exit 0, empty output) is neve
chmodSync(join(stubDir, "git"), 0o755);
const git = (cwd: string, ...args: string[]) =>
Bun.spawnSync(["git", "-c", "user.email=t@t", "-c", "user.name=t", ...args], { cwd });
Bun.spawnSync(["git", "-c", "user.email=t@t", "-c", "user.name=t", ...args], { cwd, timeout: 30_000 });
const cwd = process.cwd();
const oldPath = process.env.PATH;
+3
View File
@@ -19,6 +19,7 @@ function run(env: Record<string, string | undefined>): Record<string, string> {
const result = spawnSync('bash', [BIN], {
env: { PATH: process.env.PATH, USERPROFILE: '', ...env } as Record<string, string>,
encoding: 'utf-8',
timeout: 30_000,
});
if (result.status !== 0) {
throw new Error(`gstack-paths failed (status ${result.status}): ${result.stderr}`);
@@ -117,6 +118,7 @@ describe('gstack-paths', () => {
{
env: { PATH: process.env.PATH, USERPROFILE: '', ...env } as Record<string, string>,
encoding: 'utf-8',
timeout: 30_000,
},
);
if (result.status !== 0) {
@@ -159,6 +161,7 @@ describe('gstack-paths', () => {
const result = spawnSync('bash', [BIN], {
env: { PATH: process.env.PATH, USERPROFILE: '', HOME: '/tmp/h' } as Record<string, string>,
encoding: 'utf-8',
timeout: 30_000,
});
const lines = result.stdout.split('\n').filter(Boolean);
for (const line of lines) {
+1
View File
@@ -26,6 +26,7 @@ function run(payload: string): { stdout: string; stderr: string; status: number
env: { ...process.env, GSTACK_HOME: tmpHome },
encoding: 'utf-8',
cwd: ROOT,
timeout: 30_000,
});
return {
stdout: res.stdout ?? '',
+2
View File
@@ -31,6 +31,7 @@ function run(...args: string[]): { stdout: string; stderr: string; status: numbe
env: { ...process.env, GSTACK_HOME: tmpHome },
encoding: 'utf-8',
cwd: ROOT,
timeout: 30_000,
});
return {
stdout: res.stdout ?? '',
@@ -45,6 +46,7 @@ function runWithStdin(input: string, ...args: string[]): { stdout: string; stder
encoding: 'utf-8',
cwd: ROOT,
input,
timeout: 30_000,
});
return {
stdout: res.stdout ?? '',
+2 -1
View File
@@ -22,6 +22,7 @@ function run(
): { code: number; stdout: string; stderr: string } {
const proc = Bun.spawnSync(["bun", BIN, ...args], {
stdin: Buffer.from(stdin),
timeout: 30_000,
});
return {
code: proc.exitCode,
@@ -88,7 +89,7 @@ describe("gstack-redact --from-file", () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "redact-file-"));
const f = path.join(dir, "spec.md");
fs.writeFileSync(f, "leaked ghp_" + "a".repeat(36));
const proc = Bun.spawnSync(["bun", BIN, "--from-file", f, "--json"]);
const proc = Bun.spawnSync(["bun", BIN, "--from-file", f, "--json"], { timeout: 30_000 });
const parsed = JSON.parse(proc.stdout.toString());
expect(parsed.findings[0].id).toBe("github.pat");
fs.rmSync(dir, { recursive: true, force: true });
+1
View File
@@ -25,6 +25,7 @@ function run(command: string, args: string[], cwd: string, home: string): Comman
cwd,
encoding: 'utf8',
env: { ...process.env, HOME: home, GSTACK_HOME: path.join(home, '.gstack') },
timeout: 30_000,
});
return {
stdout: result.stdout ?? '',
@@ -919,6 +919,7 @@ describe('prune-stale', () => {
const live = mkCanon(tmpDir, 'live-worktree');
execSync(`'${path.join(ROOT, 'bin', 'gstack-config')}' set plan_tune_hooks no`, {
env: { ...process.env, GSTACK_STATE_ROOT: tmpDir },
timeout: 30_000,
});
fs.writeFileSync(settingsFile, JSON.stringify({
hooks: {
+2 -1
View File
@@ -172,12 +172,13 @@ describe('gstack-slug — outermost project-root resolution', () => {
// succeeds. (The script's step 2 reads the remote when there's no cache.)
const gitInit = spawnSync('git', ['init', '-q', '-b', 'main', projectRoot], {
encoding: 'utf8',
timeout: 30_000,
});
expect(gitInit.status).toBe(0);
const gitRemote = spawnSync(
'git',
['-C', projectRoot, 'remote', 'add', 'origin', 'https://github.com/foo/bar.git'],
{ encoding: 'utf8' },
{ encoding: 'utf8', timeout: 30_000 },
);
expect(gitRemote.status).toBe(0);
+3
View File
@@ -26,6 +26,7 @@ function runSlug(cwd: string, home: string) {
return spawnSync([SLUG_BIN], {
cwd,
env: { ...process.env, HOME: home },
timeout: 30_000,
});
}
@@ -77,6 +78,7 @@ describe('slug cache hygiene', () => {
const r = spawnSync(['bash', SLUG_BIN], {
cwd: os.tmpdir(),
env: { ...process.env, GSTACK_HOME: home, GSTACK_PROJECT_SLUG: 'override-slug' },
timeout: 30_000,
});
expect(r.stdout.toString()).toContain('SLUG=override-slug');
expect(fs.existsSync(path.join(home, 'slug-cache'))).toBe(false);
@@ -95,6 +97,7 @@ describe('slug cache hygiene', () => {
const r = spawnSync(['bash', SLUG_BIN], {
cwd: os.tmpdir(),
env: { ...ambient, GSTACK_HOME: home },
timeout: 30_000,
});
expect(r.exitCode).toBe(0);
const entries = fs.readdirSync(path.join(home, 'slug-cache'));
+1
View File
@@ -54,6 +54,7 @@ function runBin(
env: cleaned,
encoding: 'utf-8',
cwd: ROOT,
timeout: 30_000,
});
return {
stdout: res.stdout ?? '',
@@ -42,6 +42,7 @@ describe('gstack-team-init required: PreToolUse hook schema (#2413)', () => {
const stdout = execSync(`bash "${hookPath}"`, {
env: { ...process.env, HOME: home },
encoding: 'utf-8',
timeout: 30_000,
});
return { status: 0, stdout, stderr: '' };
} catch (err) {
@@ -64,6 +64,7 @@ function run(opts: { env?: Record<string, string> } = {}) {
env,
encoding: 'utf-8',
cwd: tmpHome,
timeout: 30_000,
});
}
@@ -136,6 +136,7 @@ function run(opts: { path?: string } = {}) {
env,
encoding: "utf-8",
cwd: tmpHome,
timeout: 30_000,
});
}
+1 -1
View File
@@ -287,7 +287,7 @@ export function verboseSkill(gitRef = 'ab66193e^'): string {
}
function execGit(args: string[]): string {
const r = spawnSync('git', args, { cwd: ROOT, encoding: 'utf-8', maxBuffer: 64 * 1024 * 1024 });
const r = spawnSync('git', args, { cwd: ROOT, encoding: 'utf-8', maxBuffer: 64 * 1024 * 1024, timeout: 30_000 });
if (r.status !== 0) throw new Error(`git ${args.join(' ')} failed: ${r.stderr}`);
return r.stdout;
}
+2 -2
View File
@@ -141,8 +141,8 @@ function discoverEvalCoverage(repoRoot: string, skills: string[]): {
function getGitInfo(repoRoot: string): { commit: string; branch: string } {
try {
const commit = execSync('git rev-parse --short HEAD', { cwd: repoRoot, encoding: 'utf-8' }).trim();
const branch = execSync('git rev-parse --abbrev-ref HEAD', { cwd: repoRoot, encoding: 'utf-8' }).trim();
const commit = execSync('git rev-parse --short HEAD', { cwd: repoRoot, encoding: 'utf-8', timeout: 30_000 }).trim();
const branch = execSync('git rev-parse --abbrev-ref HEAD', { cwd: repoRoot, encoding: 'utf-8', timeout: 30_000 }).trim();
return { commit, branch };
} catch {
return { commit: 'unknown', branch: 'unknown' };
+1 -1
View File
@@ -183,7 +183,7 @@ export async function runCodexSkill(opts: {
const name = skillName || path.basename(skillDir) || 'gstack';
// Check if codex binary exists
const whichResult = Bun.spawnSync(['which', 'codex']);
const whichResult = Bun.spawnSync(['which', 'codex'], { timeout: 30_000 });
if (whichResult.exitCode !== 0) {
return {
output: 'SKIP: codex binary not found',
+1 -1
View File
@@ -111,7 +111,7 @@ export async function runGeminiSkill(opts: {
const startTime = Date.now();
// Check if gemini binary exists
const whichResult = Bun.spawnSync(['which', 'gemini']);
const whichResult = Bun.spawnSync(['which', 'gemini'], { timeout: 30_000 });
if (whichResult.exitCode !== 0) {
return {
output: 'SKIP: gemini binary not found',
+4 -2
View File
@@ -31,7 +31,7 @@ const MAX_BODY = 64 * 1024;
const GUARD_RE = /^\s*(?::\s*"\$\{)?BASH_COMPAT(?:[:=]|\}")/m;
function trackedShellScripts(): string[] {
const out = execSync('git ls-files', { cwd: ROOT, encoding: 'utf-8', maxBuffer: 32 * 1024 * 1024 });
const out = execSync('git ls-files', { cwd: ROOT, encoding: 'utf-8', maxBuffer: 32 * 1024 * 1024, timeout: 30_000 });
return out
.split('\n')
.map((s) => s.trim())
@@ -88,6 +88,7 @@ describe('heredoc pipe-deadlock guard', () => {
test('the guard actually moves the body off the pipe', () => {
const bash = spawnSync('bash', ['-c', 'echo "${BASH_VERSINFO[0]}.${BASH_VERSINFO[1]}"'], {
encoding: 'utf-8',
timeout: 30_000,
});
const version = (bash.stdout ?? '').trim();
const [maj, min] = version.split('.').map((n) => parseInt(n, 10));
@@ -102,6 +103,7 @@ describe('heredoc pipe-deadlock guard', () => {
// probe would answer OTHER for an unobservable fd. Skip rather than fail.
const devStdin = spawnSync('bash', ['-c', '[ -e /dev/stdin ] && echo yes || echo no'], {
encoding: 'utf-8',
timeout: 30_000,
});
if ((devStdin.stdout ?? '').trim() !== 'yes') return;
@@ -114,7 +116,7 @@ $body
EOF
`;
const run = (guard: string) =>
(spawnSync('bash', ['-c', probe(guard)], { encoding: 'utf-8' }).stdout ?? '').trim();
(spawnSync('bash', ['-c', probe(guard)], { encoding: 'utf-8', timeout: 30_000 }).stdout ?? '').trim();
expect(run('')).toBe('PIPE');
expect(run('BASH_COMPAT=50')).toBe('TEMPFILE');
+2 -2
View File
@@ -330,7 +330,7 @@ describe('host-config-export.ts CLI', () => {
function run(...args: string[]): { stdout: string; stderr: string; exitCode: number } {
const result = Bun.spawnSync(['bun', 'run', EXPORT_SCRIPT, ...args], {
cwd: ROOT, stdout: 'pipe', stderr: 'pipe',
cwd: ROOT, stdout: 'pipe', stderr: 'pipe', timeout: 30_000,
});
return {
stdout: result.stdout.toString().trim(),
@@ -445,7 +445,7 @@ describe('golden-file regression', () => {
for (const host of ['codex', 'factory']) {
const result = Bun.spawnSync(
['bun', 'run', 'scripts/gen-skill-docs.ts', '--host', host, '--out-dir', GOLDEN_OUT],
{ cwd: ROOT },
{ cwd: ROOT, timeout: 120_000 },
);
if (result.exitCode !== 0) {
throw new Error(
+6 -4
View File
@@ -105,7 +105,7 @@ describe('gstack-ios-qa-regen', () => {
const workDir = mkdtempSync(join(tmpdir(), 'ios-qa-regen-'));
workDirs.push(workDir);
const { launcher } = copyIntoFakeInstall(workDir);
const result = spawnSync('bash', [launcher, '--app-source', workDir], { encoding: 'utf8' });
const result = spawnSync('bash', [launcher, '--app-source', workDir], { encoding: 'utf8', timeout: 30_000 });
expect(result.status).toBe(2);
expect(result.stderr).toContain('both --app-source and --bridge-dir are required');
@@ -134,6 +134,7 @@ describe('gstack-ios-qa-regen', () => {
], {
encoding: 'utf8',
env: { ...process.env, PATH: `${fakeBin}:${process.env.PATH ?? ''}` },
timeout: 30_000,
});
expect(result.status).toBe(17);
@@ -184,7 +185,7 @@ final class AppState {
GEN_ACCESSORS_REV: 'regen-test',
};
const args = [launcher, '--app-source', appSource, '--bridge-dir', bridgeDir];
const first = spawnSync('bash', args, { encoding: 'utf8', env });
const first = spawnSync('bash', args, { encoding: 'utf8', env, timeout: 30_000 });
expect(first.status).toBe(0);
expect(first.stderr).toBe('');
@@ -232,10 +233,11 @@ final class AppState {
expect(installedContents).not.toContain('FORBIDDEN-STATE-SENTINEL');
expect(installedContents).not.toContain('OBSOLETE-HARNESS-SENTINEL');
const swiftAvailable = spawnSync('swift', ['--version'], { encoding: 'utf8' }).status === 0;
const swiftAvailable = spawnSync('swift', ['--version'], { encoding: 'utf8', timeout: 30_000 }).status === 0;
if (swiftAvailable) {
const dump = spawnSync('swift', ['package', 'dump-package', '--package-path', bridgeDir], {
encoding: 'utf8',
timeout: 30_000,
});
expect(dump.status).toBe(0);
const manifest = JSON.parse(dump.stdout) as { targets: Array<{ name: string }> };
@@ -248,7 +250,7 @@ final class AppState {
const firstHash = treeHash(bridgeDir, generatedDir);
const firstAccessorHash = accessor.match(/accessorHash: "([a-f0-9]+)"/)?.[1];
const second = spawnSync('bash', args, { encoding: 'utf8', env });
const second = spawnSync('bash', args, { encoding: 'utf8', env, timeout: 30_000 });
expect(second.status).toBe(0);
expect(second.stderr).toBe('');
expect(second.stdout).toContain('gen-accessors: cache hit');
+1 -1
View File
@@ -37,7 +37,7 @@ function runSearch(args: string = ''): string {
timeout: 15000,
};
try {
return execSync(`${BIN}/gstack-learnings-search ${args}`, execOpts).trim();
return execSync(`${BIN}/gstack-learnings-search ${args}`, execOpts).trim(); // timeout via execOpts
} catch {
return '';
}
+1
View File
@@ -53,6 +53,7 @@ function runHook(stdin: object): { stdout: string; stderr: string; status: numbe
input: JSON.stringify({ ...stdin, cwd: fixtureCwd }),
encoding: 'utf-8',
cwd: ROOT,
timeout: 30_000,
});
let parsed: any = null;
try { parsed = JSON.parse(res.stdout || '{}'); } catch {}
+1
View File
@@ -81,6 +81,7 @@ function run(extraEnv: Record<string, string> = {}, input = ''): { code: number;
encoding: 'utf-8',
input,
cwd: tmpHome,
timeout: 30_000,
});
return { code: r.status ?? -1, stdout: r.stdout || '', stderr: r.stderr || '' };
}
+1
View File
@@ -97,6 +97,7 @@ function run(extraEnv: Record<string, string> = {}): {
},
encoding: 'utf-8',
cwd: tmpHome,
timeout: 30_000,
});
return { code: r.status ?? -1, stdout: r.stdout || '', stderr: r.stderr || '' };
}
+1 -1
View File
@@ -99,7 +99,7 @@ const SCAN_PATHS = [
function grepRefs(pattern: string): string[] {
const args = ['-rn', '--', pattern, ...SCAN_PATHS.map((p) => path.join(ROOT, p))];
const r = spawnSync('grep', args, { encoding: 'utf-8' });
const r = spawnSync('grep', args, { encoding: 'utf-8', timeout: 30_000 });
// grep exits 1 when no matches — that's fine for our purposes.
const lines = (r.stdout || '').split('\n').filter((l) => l.trim().length > 0);
return lines
+1 -1
View File
@@ -47,7 +47,7 @@ const GATE_PATTERNS = [
];
function trackedTestFiles(): string[] {
const out = spawnSync('git', ['ls-files', '*.test.ts'], { cwd: ROOT, encoding: 'utf-8' });
const out = spawnSync('git', ['ls-files', '*.test.ts'], { cwd: ROOT, encoding: 'utf-8', timeout: 30_000 });
if (out.status !== 0) throw new Error(`git ls-files failed: ${out.stderr}`);
return out.stdout.split('\n').filter(Boolean);
}
+1 -1
View File
@@ -55,7 +55,7 @@ function runBin(
}
env.GSTACK_STATE_ROOT = stateRoot;
delete env.GSTACK_HOME;
const res = spawnSync(bin, args, { env, encoding: 'utf-8', cwd: ROOT });
const res = spawnSync(bin, args, { env, encoding: 'utf-8', cwd: ROOT, timeout: 30_000 });
return {
stdout: res.stdout ?? '',
stderr: res.stderr ?? '',
+7 -7
View File
@@ -568,15 +568,15 @@ describe('end-to-end pipeline (binaries working together)', () => {
ts: `2026-04-0${i + 1}T10:00:00Z`,
}),
],
{ env, cwd: ROOT, encoding: 'utf-8' },
{ env, cwd: ROOT, encoding: 'utf-8', timeout: 30_000 },
);
expect(r.status).toBe(0);
}
const derive = spawnSync(devBin, ['--derive'], { env, cwd: ROOT, encoding: 'utf-8' });
const derive = spawnSync(devBin, ['--derive'], { env, cwd: ROOT, encoding: 'utf-8', timeout: 30_000 });
expect(derive.status).toBe(0);
const profileOut = spawnSync(devBin, ['--profile'], { env, cwd: ROOT, encoding: 'utf-8' });
const profileOut = spawnSync(devBin, ['--profile'], { env, cwd: ROOT, encoding: 'utf-8', timeout: 30_000 });
const p = JSON.parse(profileOut.stdout);
expect(p.inferred.sample_size).toBe(5);
expect(p.inferred.values.scope_appetite).toBeGreaterThan(0.5);
@@ -598,13 +598,13 @@ describe('end-to-end pipeline (binaries working together)', () => {
'--write',
JSON.stringify({ question_id: 'fake-id', preference: 'never-ask', source: 'inline-tool-output' }),
],
{ env, cwd: ROOT, encoding: 'utf-8' },
{ env, cwd: ROOT, encoding: 'utf-8', timeout: 30_000 },
);
expect(r.status).toBe(2);
expect(r.stderr).toContain('poisoning');
// Verify no preference was written
const read = spawnSync(prefBin, ['--read'], { env, cwd: ROOT, encoding: 'utf-8' });
const read = spawnSync(prefBin, ['--read'], { env, cwd: ROOT, encoding: 'utf-8', timeout: 30_000 });
const prefs = JSON.parse(read.stdout);
expect(prefs['fake-id']).toBeUndefined();
} finally {
@@ -633,11 +633,11 @@ describe('end-to-end pipeline (binaries working together)', () => {
);
// Migrate
const m = spawnSync(devBin, ['--migrate'], { env, cwd: ROOT, encoding: 'utf-8' });
const m = spawnSync(devBin, ['--migrate'], { env, cwd: ROOT, encoding: 'utf-8', timeout: 30_000 });
expect(m.status).toBe(0);
// Legacy shim should still return the same KEY: VALUE shape
const shimOut = spawnSync(shimBin, [], { env, cwd: ROOT, encoding: 'utf-8' });
const shimOut = spawnSync(shimBin, [], { env, cwd: ROOT, encoding: 'utf-8', timeout: 30_000 });
expect(shimOut.status).toBe(0);
expect(shimOut.stdout).toContain('SESSION_COUNT: 3');
expect(shimOut.stdout).toContain('TIER: welcome_back');
+2 -2
View File
@@ -5,7 +5,7 @@ import * as path from 'path';
const HELPER = path.join(import.meta.dir, '..', 'bin', 'gstack-pr-title-rewrite.sh');
function rewrite(version: string, title: string): { stdout: string; status: number; stderr: string } {
const r = spawnSync(HELPER, [version, title], { encoding: 'utf-8' });
const r = spawnSync(HELPER, [version, title], { encoding: 'utf-8', timeout: 30_000 });
return { stdout: (r.stdout ?? '').trimEnd(), status: r.status ?? -1, stderr: r.stderr ?? '' };
}
@@ -54,7 +54,7 @@ describe('gstack-pr-title-rewrite', () => {
});
test('errors on missing args', () => {
const r = spawnSync(HELPER, ['1.2.3.4'], { encoding: 'utf-8' });
const r = spawnSync(HELPER, ['1.2.3.4'], { encoding: 'utf-8', timeout: 30_000 });
expect(r.status).not.toBe(0);
});
+1 -1
View File
@@ -28,7 +28,7 @@ function detect(cwd: string): string {
return execFileSync(DETECT, [], { cwd, encoding: 'utf-8', env: GIT_ENV }).trim();
}
function git(cwd: string, args: string) {
execSync(`git ${args}`, { cwd, env: GIT_ENV, stdio: 'ignore' });
execSync(`git ${args}`, { cwd, env: GIT_ENV, stdio: 'ignore', timeout: 30_000 });
}
let tmp: string;

Some files were not shown because too many files have changed in this diff Show More