{"record":{"id":"69693a6a17ae1098","repo":"vitest-dev/vitest","slug":"process-exit-unexpectedly-called-with-code","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/base.ts","lineNumber":31,"sourceCode":"import { VitestEvaluatedModules } from '../moduleRunner/evaluatedModules'\nimport { createNodeImportMeta } from '../moduleRunner/moduleRunner'\nimport { startVitestModuleRunner } from '../moduleRunner/startVitestModuleRunner'\nimport { run } from '../runBaseTests'\nimport { setupEnv } from '../setup-common'\nimport { getSafeWorkerState, provideWorkerState } from '../utils'\n\nlet _moduleRunner: TestModuleRunner\n\nconst evaluatedModules = new VitestEvaluatedModules()\nconst moduleExecutionInfo = new Map()\n\nasync function startModuleRunner(options: ContextModuleRunnerOptions): Promise<TestModuleRunner> {\n  if (_moduleRunner) {\n    return _moduleRunner\n  }\n\n  process.exit = (code = process.exitCode || 0): never => {\n    throw new Error(`process.exit unexpectedly called with \"${code}\"`)\n  }\n  const state = () => getSafeWorkerState() || options.state\n\n  listenForErrors(state)\n\n  if (options.state.config.experimental.viteModuleRunner === false) {\n    const root = options.state.config.root\n    let mocker: TestModuleMocker | undefined\n    if (options.state.config.experimental.nodeLoader !== false) {\n      // this additionally imports acorn/magic-string\n      const { NativeModuleMocker } = await import('../moduleRunner/nativeModuleMocker')\n      mocker = new NativeModuleMocker({\n        async resolveId(id, importer) {\n          // TODO: use import.meta.resolve instead\n          return state().rpc.resolve(id, importer, '__vitest__')\n        },\n        root,\n        moduleDirectories: state().config.deps.moduleDirectories || ['/node_modules/'],","sourceCodeStart":13,"sourceCodeEnd":49,"githubUrl":"https://github.com/vitest-dev/vitest/blob/1fa9837ec26533512fdcad8baebf249771bd340a/packages/vitest/src/runtime/workers/base.ts#L13-L49","documentation":"To keep workers alive across test files, Vitest reassigns process.exit in startModuleRunner to a function that throws instead of terminating the process. Any call to process.exit(code) inside test code or an imported module therefore raises this error with the requested code. The throw is caught by Vitest's worker error handling and reported as a test failure rather than killing the worker.","triggerScenarios":"Application code or a dependency under test invokes process.exit() (commonly process.exit(1) on a fatal error). Because the override throws synchronously, the call site sees an exception with message 'process.exit unexpectedly called with \"<code>\"'.","commonSituations":"Testing a CLI or library that calls process.exit on invalid input or fatal errors; a dependency that hard-exits on missing config; or a test helper that uses process.exit. The override is intentional so workers are recycled rather than dying.","solutions":["Spy on process.exit in the test and override it to throw a recognizable error: vi.spyOn(process, 'exit').mockImplementation(code => { throw new Error('exit ' + code) }).","Refactor the code under test to throw an Error instead of calling process.exit so it is testable.","Inject an exit hook/callback the code calls instead of process.exit directly (dependency injection).","Run the exiting code in a subprocess when you genuinely need the real exit behavior."],"exampleFix":"// before: code under test\ndeleteDb() ; process.exit(1)\n\n// after: throw, or spy in the test\n// test:\nvi.spyOn(process, 'exit').mockImplementation(() => { throw new Error('exit') })\nexpect(() => deleteDb()).toThrow()","handlingStrategy":"validation","validationCode":"// In tests, replace process.exit before exercising the code\nimport { vi } from 'vitest'\nbeforeEach(() => {\n  vi.spyOn(process, 'exit').mockImplementation((code) => {\n    throw new Error(`process.exit(${code})`)\n  })\n})","typeGuard":null,"tryCatchPattern":"try {\n  runCodeUnderTest()\n} catch (e) {\n  if (/process\\.exit unexpectedly called/.test(e.message)) {\n    // assert the intended exit code instead of crashing\n  } else throw e\n}","preventionTips":["Never call process.exit in test helpers; throw instead.","Spy on process.exit in tests that exercise error paths.","Inject an exit hook into code under test for testability."],"tags":["process-exit","worker","lifecycle","mocking"],"backgroundTag":null,"analyzedSha":"1fa9837ec26533512fdcad8baebf249771bd340a","analyzedAt":"2026-08-11T16:11:39.638Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}