{"record":{"id":"e9fc5fb19750469d","repo":"vitest-dev/vitest","slug":"process-exit-unexpectedly-called-with-code-e9fc5f","errorCode":null,"errorMessage":"process.exit unexpectedly called with \"${code}\"","messagePattern":"process\\.exit unexpectedly called with \"(.+?)\"","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/vitest/src/runtime/workers/vm.ts","lineNumber":122,"sourceCode":"  // TODO: don't hardcode setImmediate in fake timers defaults\n  context.setImmediate = setImmediate\n  context.clearImmediate = clearImmediate\n\n  const stubs = getDefaultRequestStubs(context)\n\n  const externalModulesExecutor = new ExternalModulesExecutor({\n    context,\n    fileMap,\n    codeCache,\n    resolveCache,\n    moduleInfoCache,\n    packageCache,\n    transform: rpc.transform,\n    viteClientModule: stubs['/@vite/client'],\n  })\n\n  process.exit = (code = process.exitCode || 0): never => {\n    throw new Error(`process.exit unexpectedly called with \"${code}\"`)\n  }\n\n  listenForErrors(() => state)\n\n  const moduleRunner = startVitestModuleRunner({\n    context,\n    evaluatedModules: state.evaluatedModules,\n    state,\n    externalModulesExecutor,\n    createImportMeta: createNodeImportMeta,\n    traces,\n  })\n\n  emitModuleRunner(moduleRunner as any)\n\n  Object.defineProperty(context, VITEST_VM_CONTEXT_SYMBOL, {\n    value: {\n      context,","sourceCodeStart":104,"sourceCodeEnd":140,"githubUrl":"https://github.com/vitest-dev/vitest/blob/1fa9837ec26533512fdcad8baebf249771bd340a/packages/vitest/src/runtime/workers/vm.ts#L104-L140","documentation":"Identical override to error [366] but installed in the vm worker setup path (runtime/workers/vm.ts). Vitest reassigns process.exit in the vm worker so that code under test calling process.exit(code) throws instead of killing the vm worker, preserving worker/caches for subsequent test files.","triggerScenarios":"Application code or a dependency invoked inside the vm context calls process.exit(). Because the vm worker overrides process.exit to throw, the call surfaces as 'process.exit unexpectedly called with \"<code>\"'.","commonSituations":"Same as [366]: testing a CLI/library that hard-exits on errors, a dependency exiting on missing config, or test helpers using process.exit, now exercised under the vm pool.","solutions":["Spy on process.exit in the test: vi.spyOn(process, 'exit').mockImplementation(() => { throw new Error('exit') }).","Refactor code under test to throw instead of process.exit.","Inject a callback/hook the code calls instead of process.exit.","Run the exiting path in a child process if real exit semantics are required."],"exampleFix":"// before: under test\nif (!cfg) process.exit(1)\n\n// after: test spies on exit\nvi.spyOn(process, 'exit').mockImplementation(c => { throw new Error('exit ' + c) })\nexpect(() => run()).toThrow('exit 1')","handlingStrategy":"validation","validationCode":"import { vi } from 'vitest'\nbeforeEach(() => {\n  vi.spyOn(process, 'exit').mockImplementation((c) => { throw new Error(`exit ${c}`) })\n})","typeGuard":null,"tryCatchPattern":"try {\n  runUnderVm()\n} catch (e) {\n  if (/process\\.exit unexpectedly called/.test(e.message)) {\n    // assert exit code; the worker stays alive\n  } else throw e\n}","preventionTips":["Refactor process.exit calls to thrown errors in code under test.","Spy on process.exit when testing error paths under the vm pool.","Run exiting code in a subprocess when real exit is needed."],"tags":["process-exit","vm","worker","lifecycle"],"backgroundTag":null,"analyzedSha":"1fa9837ec26533512fdcad8baebf249771bd340a","analyzedAt":"2026-08-11T16:11:39.638Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}