fix(evals): preflight fails fast on spawn error, timeout, and exit 127

The ping only grepped stdout for two connection strings — a missing
claude binary, a 30s timeout kill, or command-not-found all returned
'ok', and the fleet then burned ~30 shard timeouts discovering the
outage one child at a time. Cross-model finding (testing specialist +
codex adversarial). Other non-zero exits stay deliberately fail-open:
a flaky preflight must not block a runnable suite; pinned both ways.
This commit is contained in:
Garry Tan
2026-08-15 17:01:41 -07:00
parent 9e72b4ac7f
commit 1e35386c54
2 changed files with 43 additions and 0 deletions
+29
View File
@@ -50,4 +50,33 @@ describe('preflightAnthropicApi', () => {
expect(result).toBe('ok');
expect(calls.length).toBe(1);
});
// Failure modes that guarantee every shard fails too must fail the
// preflight — previously a missing binary, a timeout kill, and exit 127
// all returned 'ok' and the fleet burned its own discovery of the outage.
const shapedSpawn = (shape: Partial<ReturnType<typeof import('child_process').spawnSync>>) =>
((() => ({ stdout: Buffer.from(''), stderr: Buffer.from(''), status: 0, ...shape })) as unknown as
typeof import('child_process').spawnSync);
test('spawn error (unlaunchable shell/binary) throws', () => {
expect(() =>
preflightAnthropicApi({}, shapedSpawn({ error: new Error('spawn sh ENOENT') })),
).toThrow(/could not run/);
});
test('timeout kill (signal set) throws', () => {
expect(() =>
preflightAnthropicApi({}, shapedSpawn({ signal: 'SIGTERM' })),
).toThrow(/timed out/);
});
test('exit 127 (claude not found) throws', () => {
expect(() =>
preflightAnthropicApi({}, shapedSpawn({ status: 127 })),
).toThrow(/not found on PATH/);
});
test('other non-zero exits stay fail-open (a flaky preflight must not block a runnable suite)', () => {
expect(preflightAnthropicApi({}, shapedSpawn({ status: 1, stdout: Buffer.from('transient') }))).toBe('ok');
});
});