test(bundle-runner): fix SIGTERM-handler registration race (1s timeout flake)#3618
Conversation
…t flake) The `timeout emits terminal reason BEFORE SIGTERM/SIGKILL grace` test flaked on PR #3617's post-merge run on main (run #25494120215). The fixture's first line is: process.on('SIGTERM', () => {}); console.log('hung'); setInterval(...); On a cold/loaded CI runner Node's startup (parse + import + this line's execution) can exceed the test's 1s timeout. SIGTERM then arrives BEFORE process.on registered the ignore-handler, so the child dies via Node's default SIGTERM behaviour and never reaches console.log('hung'). Total elapsed lands at ~1.1s instead of the expected ~11s (1s timeout + 10s SIGKILL grace), the `[HANG] hung` assertion fails, and the test never exercises the SIGKILL escalation it's actually here to validate. Fix: bump TIMEOUT_MS from 1000 to 3000. Typical Node startup is 50-200ms and even loaded GitHub-hosted runners shouldn't exceed 1-2s, so 3s gives comfortable cold-start margin while still exercising the same timeout → SIGTERM → 10s grace → SIGKILL flow the test validates. The test's existing 20s elapsedMs cap remains comfortably above the new worst-case (3s + 10s grace + overhead ~= 14s). Also relaxed the regex `/timeout after 1s — sending SIGTERM/` to `/timeout after \d+s — sending SIGTERM/` so a future timeout bump doesn't require a coordinated regex update — the assertion's purpose is "Failed line names the timeout-after-N pattern", not the literal N. Verification: ran tests/bundle-runner.test.mjs 5 times locally, all 9 tests pass each run, no flakes. The 1s value was a real timing bug, not just a slow runner — it was flaky because there's no contract that user code in the fixture has run before the timer fires. The fixture's SIGTERM handler MUST be registered before SIGTERM arrives for the test's "ignores SIGTERM, must SIGKILL" contract to be exercised, and the 1s window didn't guarantee that.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryAddresses a documented flake in the
Confidence Score: 5/5Safe to merge; the change is confined to a single test file and corrects a real structural flake without weakening any meaningful assertion. The timeout bump from 1 s to 3 s directly addresses the documented race between Node startup and SIGTERM delivery, and the 20 s upper-bound cap still gives comfortable headroom above the new ~13 s worst-case path. The regex relaxation preserves the test's semantic intent while removing a brittle literal. No production code is touched. No files require special attention. Important Files Changed
Sequence DiagramsequenceDiagram
participant Test as Test (bundle-runner.test.mjs)
participant Runner as _bundle-runner.mjs
participant Child as _bundle-fixture-hang.mjs
Test->>Runner: "runBundleWith([HANG, timeoutMs=3000])"
Runner->>Child: spawn (Node.js startup)
Note over Child: ~50-200ms: parse + register process.on('SIGTERM', () => {})
Child-->>Runner: stdout "hung"
Runner-->>Test: [HANG] hung (streamed live)
Note over Runner: 3000ms timeout fires
Runner->>Child: SIGTERM
Note over Child: Handler installed - SIGTERM ignored
Runner-->>Test: Failed after Xs: timeout after 3s - sending SIGTERM
Note over Runner: 10s SIGKILL grace window
Runner->>Child: SIGKILL
Runner-->>Test: Did not exit on SIGTERM - SIGKILL
Runner-->>Test: exit code 1
Note over Test: assert elapsedMs < 20_000 (~13s actual)
Reviews (1): Last reviewed commit: "test(bundle-runner): fix SIGTERM-handler..." | Re-trigger Greptile |
…t flake) (koala73#3618) The `timeout emits terminal reason BEFORE SIGTERM/SIGKILL grace` test flaked on PR koala73#3617's post-merge run on main (run #25494120215). The fixture's first line is: process.on('SIGTERM', () => {}); console.log('hung'); setInterval(...); On a cold/loaded CI runner Node's startup (parse + import + this line's execution) can exceed the test's 1s timeout. SIGTERM then arrives BEFORE process.on registered the ignore-handler, so the child dies via Node's default SIGTERM behaviour and never reaches console.log('hung'). Total elapsed lands at ~1.1s instead of the expected ~11s (1s timeout + 10s SIGKILL grace), the `[HANG] hung` assertion fails, and the test never exercises the SIGKILL escalation it's actually here to validate. Fix: bump TIMEOUT_MS from 1000 to 3000. Typical Node startup is 50-200ms and even loaded GitHub-hosted runners shouldn't exceed 1-2s, so 3s gives comfortable cold-start margin while still exercising the same timeout → SIGTERM → 10s grace → SIGKILL flow the test validates. The test's existing 20s elapsedMs cap remains comfortably above the new worst-case (3s + 10s grace + overhead ~= 14s). Also relaxed the regex `/timeout after 1s — sending SIGTERM/` to `/timeout after \d+s — sending SIGTERM/` so a future timeout bump doesn't require a coordinated regex update — the assertion's purpose is "Failed line names the timeout-after-N pattern", not the literal N. Verification: ran tests/bundle-runner.test.mjs 5 times locally, all 9 tests pass each run, no flakes. The 1s value was a real timing bug, not just a slow runner — it was flaky because there's no contract that user code in the fixture has run before the timer fires. The fixture's SIGTERM handler MUST be registered before SIGTERM arrives for the test's "ignores SIGTERM, must SIGKILL" contract to be exercised, and the 1s window didn't guarantee that.
Summary
Fixes the
timeout emits terminal reason BEFORE SIGTERM/SIGKILL gracetest flake that hit PR #3617's post-merge run on main (run #25494120215, 1 of 8114 tests failing).The fixture's first line is:
On a cold/loaded CI runner Node's startup (parse + import + execute that line) can exceed the test's 1s timeout. SIGTERM then arrives before
process.onregistered the ignore-handler, so the child dies via Node's default SIGTERM behaviour and never reachesconsole.log('hung'). Total elapsed lands at ~1.1s instead of the expected ~11s (1s timeout + 10s SIGKILL grace), the[HANG] hungassertion fails, and the test never exercises the SIGKILL escalation it's actually here to validate.Fix: bump
TIMEOUT_MSfrom 1000 → 3000. Typical Node startup is 50–200ms; even loaded GitHub-hosted runners shouldn't exceed 1–2s, so 3s gives comfortable cold-start margin while still exercising the sametimeout → SIGTERM → 10s grace → SIGKILLflow. The test's existing 20selapsedMscap remains comfortably above the new worst-case (3s + 10s grace + overhead ≈ 14s).Also relaxed the regex
/timeout after 1s — sending SIGTERM/to/timeout after \d+s — sending SIGTERM/so a future timeout bump doesn't require a coordinated regex update — the assertion's purpose is "Failed line names the timeout-after-N pattern", not the literal N.Why it was a real bug, not "slow runner"
The 1s value had no contract that the fixture's user code had run before the timer fired. The fixture's SIGTERM handler MUST be registered before SIGTERM arrives for the test's "ignores SIGTERM, must SIGKILL" contract to be exercised. The 1s window didn't guarantee that — the test was structurally flaky. 3s makes the contract reliably testable on real-world runner conditions.
Test plan
npx tsx --test tests/bundle-runner.test.mjs— ran 5×, all 9 tests pass each run, no flakesnpm run typecheck— clean