Skip to content

test_runner: gate event-loop drain and on_exit to node:test, drop BUN_TEST_DRAIN_EVENT_LOOP - #35401

Open
robobun wants to merge 5 commits into
mainfrom
farm/46ad8752/node-test-drain-event-loop
Open

test_runner: gate event-loop drain and on_exit to node:test, drop BUN_TEST_DRAIN_EVENT_LOOP#35401
robobun wants to merge 5 commits into
mainfrom
farm/46ad8752/node-test-drain-event-loop

test_runner: gate event-loop drain and on_exit to node:test, drop BUN…

98b8c85
Select commit
Loading
Failed to load commit list.
Claude / Claude Code Review completed Jul 24, 2026 in 28m 37s

Code review found 1 important issue

Found 4 candidates, confirmed 2. See review comments for details.

Details

Severity Count
🔴 Important 1
🟡 Nit 1
🟣 Pre-existing 0
Severity File:Line Issue
🔴 Important src/runtime/cli/test_command.rs:3384-3386 Per-file on_before_exit() drain is unsound in multi-file bun test runs
🟡 Nit src/js/node/test.ts:479 run() children that don't import node:test lose drain and exit handlers

Annotations

Check failure on line 3386 in src/runtime/cli/test_command.rs

See this annotation in the file changed.

@claude claude / Claude Code Review

Per-file on_before_exit() drain is unsound in multi-file bun test runs

The per-file `on_before_exit()` drain at line 3384 is unsound in multi-file `bun test` runs: `node_test_module_used` is process-lifetime state that is never reset between files, and even when correctly set for a node:test file, the drain's `while is_event_loop_alive()` loop sees ref'd handles left by *earlier* bun:test files (a leaked `setInterval`, an unclosed `Bun.serve`) and hangs the whole process. The removed env var was explicitly "Opt-in; one file per process" — its only setters (`runner.

Check warning on line 479 in src/js/node/test.ts

See this annotation in the file changed.

@claude claude / Claude Code Review

run() children that don't import node:test lose drain and exit handlers

Dropping `BUN_TEST_DRAIN_EVENT_LOOP` from the `runOneFile` env means a `run()` child whose target file never imports `node:test` (e.g. a plain `common.mustCall()` + async-callback script) no longer drains its event loop or dispatches `process.on('exit')` — `node_test_module_used` is only set via `getRootNode()`, which such a file never reaches. Node's `run({isolation:'process'})` runs each file as a full node process that always drains and fires `'exit'` regardless of whether it imported `node:t