vitest-dev/vitest · error · Error

process.exit unexpectedly called with "${code}"

Error message

process.exit unexpectedly called with "${code}"

What it means

Identical guard to the base pool (error 382) but installed in runVmTests (vm.ts:114-116) for the VM pool: process.exit is overridden to throw so code under test cannot terminate the VM worker. The thrown Error reports the exit code that was requested.

Source

Thrown at packages/vitest/src/runtime/workers/vm.ts:115

  // TODO: don't hardcode setImmediate in fake timers defaults
  context.setImmediate = setImmediate
  context.clearImmediate = clearImmediate

  const stubs = getDefaultRequestStubs(context)

  const externalModulesExecutor = new ExternalModulesExecutor({
    context,
    fileMap,
    codeCache,
    resolveCache,
    moduleInfoCache,
    packageCache,
    transform: rpc.transform,
    viteClientModule: stubs['/@vite/client'],
  })

  process.exit = (code = process.exitCode || 0): never => {
    throw new Error(`process.exit unexpectedly called with "${code}"`)
  }

  listenForErrors(() => state)

  const moduleRunner = startVitestModuleRunner({
    context,
    evaluatedModules: state.evaluatedModules,
    state,
    externalModulesExecutor,
    createImportMeta: createNodeImportMeta,
    traces,
  })

  emitModuleRunner(moduleRunner as any)

  Object.defineProperty(context, VITEST_VM_CONTEXT_SYMBOL, {
    value: {
      context,

View on GitHub (pinned to d568f8ce37)

Solutions

  1. Spy on process.exit (vi.spyOn(process,'exit').mockImplementation(...)) so it no-ops or throws an assertion-friendly error.
  2. Refactor the code under test to throw instead of exit.
  3. Mock the offending dependency with vi.mock if it cannot be changed.

Example fix

// before: process.exit in code under test
function fail(msg) { console.error(msg); process.exit(1) }

// after: throw, or stub in the test
function fail(msg) { throw new Error(msg) }
// test alternative
const exit = vi.spyOn(process, 'exit').mockImplementation((c) => { throw new Error(`exit ${c}`) })
Defensive patterns

Strategy: try-catch

Try / catch

const exitSpy = vi.spyOn(process, 'exit')
  .mockImplementation((code?: number) => {
    throw new Error(`process.exit(${code}) called in vm pool`)
  })
try {
  await runTestInVm()
} catch (e) {
  expect((e as Error).message).toMatch(/process\.exit/)
} finally {
  exitSpy.mockRestore()
}

Prevention

When it happens

Trigger: Test code or an imported module calls process.exit() while running under pool 'vmThreads' or 'vmForks'. The override is installed after the VM context is set up and stays active for the run.

Common situations: Testing CLI/error-path code that hard-exits; a dependency that calls process.exit on a deprecation warning or missing native binding; test helpers that call process.exit to signal failure.

Related errors


AI-assisted analysis of vitest-dev/vitest@d568f8ce37 (2026-08-03). Data as JSON: /data/errors/e9fc5fb19750469d.json. Report an issue: GitHub.