You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Vitest fork workers are not properly cleaned up when test runs are interrupted or exit unexpectedly, resulting in orphaned processes (PPID 1) that persist indefinitely after the parent test runner exits.
Environment
Vitest Version: 3.2.4
Node Version: Current system
OS: macOS (Darwin 24.6.0)
Pool Configuration: forks with maxForks: 4
Evidence
Discovered 7 running Vitest processes when configuration limits to 4:
$ ps -p 38327,38328,38329,38330,40284,41935,41936 -o pid,ppid,lstart,command
PID PPID STARTED COMMAND
38327 1 Wed Nov 19 08:54:28 2025 node (vitest 1)
38328 1 Wed Nov 19 08:54:28 2025 node (vitest 2)
38329 1 Wed Nov 19 08:54:28 2025 node (vitest 3)
38330 1 Wed Nov 19 08:54:28 2025 node (vitest 4)
40284 1 Wed Nov 19 08:54:40 2025 node (vitest 3)
41935 1 Wed Nov 19 08:54:46 2025 node (vitest 2)
41936 1 Wed Nov 19 08:54:46 2025 node (vitest 1)
Analysis:
All processes show PPID 1 (init/launchd) = orphaned from dead parent
Three distinct batches from three separate interrupted test runs:
First run (08:54:28): 4 workers (vitest 1-4) - all orphaned
Second run (08:54:40): 1 orphaned worker (vitest 3)
Third run (08:54:46): 2 orphaned workers (vitest 1-2)
Current Configuration
// vitest.config.js:18-29exportdefaultdefineConfig({test: {pool: "forks",poolOptions: {forks: {maxForks: 4,// Controlled parallelismminForks: 1,},},forceExit: true,// Only affects main process, not workerstestTimeout: 10000,hookTimeout: 10000,},});
Root Cause
Worker processes don't receive termination signals when parent Vitest process exits unexpectedly (Ctrl+C, crash, forced exit)
forceExit: true only applies to the main Vitest process, not fork workers
Process cleanup handlers in test setup files run within workers (not in parent), so they can't kill sibling workers
Problem Description
Vitest fork workers are not properly cleaned up when test runs are interrupted or exit unexpectedly, resulting in orphaned processes (PPID 1) that persist indefinitely after the parent test runner exits.
Environment
forkswithmaxForks: 4Evidence
Discovered 7 running Vitest processes when configuration limits to 4:
Analysis:
Current Configuration
Root Cause
forceExit: trueonly applies to the main Vitest process, not fork workersCurrent Cleanup Logic (Insufficient)
Why this doesn't work:
global.childProcessesImpact
Workarounds
Immediate: Manual cleanup
pkill -9 -f "node \\(vitest"Short-term: Pre-test cleanup hook
{ "scripts": { "pretest": "pkill -9 -f 'node (vitest' || true", "test": "vitest run" } }Medium-term: Test wrapper script
Alternative: Switch to threads pool
Trade-off: Threads pool has better cleanup but less process isolation (may not work for CommonJS isolation requirements).
Proposed Solutions
Option 1: Pre-test cleanup (Quick fix)
Add automated cleanup before test runs to prevent accumulation.
Pros:
Cons:
Option 2: Switch to threads pool (If isolation not critical)
Migrate from
pool: 'forks'topool: 'threads'.Pros:
Cons:
Option 3: Monitor Vitest upstream (Long-term)
Track vitest-dev/vitest#3909 for official fix.
Pros:
Cons:
Acceptance Criteria
ps aux | grep vitestshows at mostmaxForksworker processesReferences
vitest.config.js:18-29test/setup.js:14-48Questions for Discussion