{"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/d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09/packages/vitest/src/runtime/workers/base.ts#L13-L49","documentation":"Vitest deliberately overrides process.exit inside the module-runner setup (base.ts:30) so that any code under test calling process.exit throws instead of killing the whole worker process. The thrown Error carries the exit code the code tried to use. This preserves test isolation so one test cannot terminate the worker and mask other failures.","triggerScenarios":"A test file or a module it imports (directly or transitively) invokes process.exit() / process.exit(code) during collection or execution under the threads/forks pool with the base environment. The override is installed in startModuleRunner and stays active while tests run.","commonSituations":"Testing a CLI module or library that calls process.exit on invalid input or error paths; importing a third-party dependency that hard-exits on a feature flag or missing config; code that uses process.exit as control flow.","solutions":["Stub process.exit in the test (vi.stubEnv/spyOn) so it resolves instead of exiting, then assert it was called.","Refactor the code under test to throw an error instead of calling process.exit, and assert on the thrown error.","If the exit is in a dependency you cannot change, mock that module via vi.mock."],"exampleFix":"// before: code under test exits\nfunction parse(args) {\n  if (!args[0]) process.exit(1)\n}\n\n// after: throw instead, or spy in the test\n// code\nfunction parse(args) {\n  if (!args[0]) throw new Error('missing arg')\n}\n// test\nconst spy = vi.spyOn(process, 'exit').mockImplementation(() => { throw new Error('exit') })\nexpect(() => parse([])).toThrow('missing arg')","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`)\n  })\ntry {\n  await runCodeUnderTest()\n} catch (e) {\n  expect((e as Error).message).toMatch(/process\\.exit/)\n} finally {\n  exitSpy.mockRestore()\n}","preventionTips":["Never call process.exit inside library code; throw a typed error instead so callers can react.","In tests, always vi.spyOn(process,'exit') before invoking code that may exit.","Audit dependencies for process.exit usage when adding them to a test runtime."],"tags":["process-exit","isolation","base-pool","test-code"],"analyzedSha":"d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09","analyzedAt":"2026-08-03T20:23:56.861Z","schemaVersion":2}