Skip to content

fix(test): stop child-process cleanup from masking real test failures - #80

Merged
jamesaphoenix merged 1 commit into
mainfrom
fix/test-cleanup-masking-failures
Aug 14, 2026
Merged

fix(test): stop child-process cleanup from masking real test failures#80
jamesaphoenix merged 1 commit into
mainfrom
fix/test-cleanup-masking-failures

Conversation

@jamesaphoenix

Copy link
Copy Markdown
Owner

What broke

CI went red on main again after #79, on ralph-watchdog-script.test.ts > exits cleanly on SIGINT.

The reported error was Timed out waiting for process 16248 to exit — but that message comes from the cleanup helper, not the test body:

  • SIGNAL_EXIT_TIMEOUT_MS (what the test itself waits) is 15s
  • The test failed after 4065ms — which is exactly terminateChild's two 2s waits
  • SIGTERM passed in 86ms in the same run, so process teardown itself was fine

Root cause

terminateChild runs from finally blocks, but its SIGKILL path awaited waitForExit without a try/catch:

proc.kill("SIGTERM")
try {
  await waitForExit(proc, 2000)
  return
} catch {
  proc.kill("SIGKILL")
  await waitForExit(proc, 2000)   // <- unguarded, throws out of finally
}

When the exit event was slow to arrive under CI load, cleanup threw and replaced whatever the test had actually asserted. We never got to see the real outcome.

Fix

Cleanup is best-effort and cannot throw. The SIGKILL wait also gets more room: SIGKILL cannot be caught, so a missing exit event inside the window is a reporting artifact rather than a process still running. The harness still reaps by pid afterwards via cleanupHarness.

Applied to both files carrying this helper (ralph-watchdog-script.test.ts, ralph-script.test.ts).

Note on the previous fix

#79 was still worth doing and is what made this diagnosable. Before it, vitest killed the test at its global 10s timeout with a generic "Test timed out in 10000ms" and no information. After it, the failure surfaced a specific message with a pid — which is what pointed at the cleanup helper rather than the watchdog script.

terminateChild runs from finally blocks, but its SIGKILL path awaited
waitForExit without a try/catch. When the exit event was slow to arrive
under CI load, cleanup threw and replaced whatever the test had actually
asserted.

That is what the SIGINT watchdog failure on main was: the reported error
was "Timed out waiting for process 16248 to exit" from the cleanup helper,
and the 4065ms duration matches its two 2s waits rather than the 15s the
test itself budgets. The real outcome of the assertion was never visible.
SIGTERM passed in 86ms in the same run, so the process teardown itself was
not the problem.

Cleanup is now best effort and cannot throw. The SIGKILL wait is also
given more room, since SIGKILL cannot be caught and a missing exit event
inside the window is a reporting artifact rather than a live process. The
harness still reaps by pid afterwards.
@jamesaphoenix
jamesaphoenix merged commit d8e2b86 into main Aug 14, 2026
2 checks passed
@jamesaphoenix
jamesaphoenix deleted the fix/test-cleanup-masking-failures branch August 14, 2026 09:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant