{"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":115,"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":97,"sourceCodeEnd":133,"githubUrl":"https://github.com/vitest-dev/vitest/blob/d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09/packages/vitest/src/runtime/workers/vm.ts#L97-L133","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Spy on process.exit (vi.spyOn(process,'exit').mockImplementation(...)) so it no-ops or throws an assertion-friendly error.","Refactor the code under test to throw instead of exit.","Mock the offending dependency with vi.mock if it cannot be changed."],"exampleFix":"// before: process.exit in code under test\nfunction fail(msg) { console.error(msg); process.exit(1) }\n\n// after: throw, or stub in the test\nfunction fail(msg) { throw new Error(msg) }\n// test alternative\nconst exit = vi.spyOn(process, 'exit').mockImplementation((c) => { throw new Error(`exit ${c}`) })","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"const exitSpy = vi.spyOn(process, 'exit')\n  .mockImplementation((code?: number) => {\n    throw new Error(`process.exit(${code}) called in vm pool`)\n  })\ntry {\n  await runTestInVm()\n} catch (e) {\n  expect((e as Error).message).toMatch(/process\\.exit/)\n} finally {\n  exitSpy.mockRestore()\n}","preventionTips":["Treat process.exit as an error in library code — throw instead.","In vm-pool tests, spy on process.exit before invoking code that might exit.","Gate process.exit behind an isMainThread/shouldExit check so tests are unaffected."],"tags":["vm-pool","process-exit","isolation","test-code"],"analyzedSha":"d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09","analyzedAt":"2026-08-03T20:23:56.861Z","schemaVersion":2}