From 959ad3451f3929d14e2f2e041b7c7bcd45d1cc1b Mon Sep 17 00:00:00 2001 From: Larry Gordon Date: Thu, 30 Jul 2026 11:23:56 -0700 Subject: [PATCH] fix(test): gave CLI-spawning tests explicit timeouts Four tests spawn the full CLI entry graph (or compile a standalone binary) and declared no timeout, so they inherited Bun's 5s default. Spawning `src/cli.ts` costs ~900ms warm on a fast machine and ~3.1s cold, so the budget is spent almost entirely on transpile. When CI runs the native bucket with OMP_TEST_CONCURRENCY=4, a cold spawn on a contended runner crosses 5s and the test fails with `timed out after 5000ms` plus a trailing `killed 1 dangling process` - the subprocess was still alive when the timeout fired. Reproduced locally by oversubscribing the box (48 concurrent runs of the same chunk): 48/48 failed with the identical signature, while 4-way concurrency - what CI actually configures - passed every time. Only the subprocess tests starve; the pure-unit tests in the same files pass. Timeouts are sized to the work, matching existing subprocess tests (read-cli-mcp-resource 30_000, acp-stdout-hygiene 60_000): 30s for CLI spawns, 60s for the `bun build --compile` case. After the change, 64 concurrent runs of all three files produce zero timeouts. `profile-cli.test.ts` gets the timeout on both spawn tests. Only the first was observed failing, because it warms the transpile cache for its sibling - that ordering is incidental and would flake if it changed. These are drift and wiring assertions, not latency assertions, so a generous ceiling costs nothing on a healthy run. --- packages/coding-agent/test/cli/completions.test.ts | 5 ++++- .../coding-agent/test/eval/process-entry-import.test.ts | 8 ++++++-- packages/coding-agent/test/profile-cli.test.ts | 8 ++++++-- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/packages/coding-agent/test/cli/completions.test.ts b/packages/coding-agent/test/cli/completions.test.ts index 3dd8dd9a6..634276086 100644 --- a/packages/coding-agent/test/cli/completions.test.ts +++ b/packages/coding-agent/test/cli/completions.test.ts @@ -224,5 +224,8 @@ describe("omp completions (integration / drift)", () => { // Hidden/default commands must NOT surface as completable subcommands. expect(stdout).not.toContain("_omp_cmd_launch"); expect(stdout).not.toContain("_omp_cmd___complete"); - }); + // Spawns the whole CLI entry graph, so the wall time is cold-transpile bound + // (~1s warm) rather than an assertion about latency. Bun's 5s default starves + // it when CI runs several test chunks in parallel on a shared runner. + }, 30_000); }); diff --git a/packages/coding-agent/test/eval/process-entry-import.test.ts b/packages/coding-agent/test/eval/process-entry-import.test.ts index cb322d593..d6bb57961 100644 --- a/packages/coding-agent/test/eval/process-entry-import.test.ts +++ b/packages/coding-agent/test/eval/process-entry-import.test.ts @@ -60,7 +60,9 @@ it("starts ordinary CLI paths without loading the native computer addon", async const [exitCode, stderr] = await Promise.all([proc.exited, new Response(proc.stderr).text()]); expect(exitCode, `${args.at(-1)}: ${stderr}`).toBe(0); } -}); + // Two cold CLI spawns (`--version`, `--help`) per run; the assertion is the exit + // code, not the wall time. +}, 30_000); it("dispatches the computer worker through the CLI host selector in a child process", async () => { const fixture = path.resolve(import.meta.dir, "../fixtures/computer-worker-cli-selector.ts"); @@ -134,4 +136,6 @@ it("keeps non-computer selectors isolated in a compiled single-entry worker host ]); expect(exitCode, stderr).toBe(0); expect(stdout).toBe('{"ok":true,"kind":"pong"}\n'); -}); + // Compiles a standalone binary with `bun build --compile` before running it, so + // this needs the same headroom as the other compile-backed tests. +}, 60_000); diff --git a/packages/coding-agent/test/profile-cli.test.ts b/packages/coding-agent/test/profile-cli.test.ts index 64c3f7aea..05b916ab8 100644 --- a/packages/coding-agent/test/profile-cli.test.ts +++ b/packages/coding-agent/test/profile-cli.test.ts @@ -270,7 +270,9 @@ describe("global --profile flag", () => { } finally { await removeWithRetries(root); } - }); + // Spawns a probe that imports the command modules, so the cost is cold + // transpile of the CLI graph, not latency under test. + }, 30_000); it("surfaces an invalid OMP_PROFILE env as a clean error, not an import crash", async () => { const root = await fs.mkdtemp(path.join(os.tmpdir(), "omp-profile-cli-env-bad-")); @@ -319,5 +321,7 @@ describe("global --profile flag", () => { } finally { await removeWithRetries(root); } - }); + // Same cold-spawn cost as the sibling above; it only escapes Bun's 5s + // default because that test warms the transpile cache first. + }, 30_000); });