{"id":"dce5a470f6421f0a","repo":"vitest-dev/vitest","slug":"expected-worker-to-be-run-in-node-worker-threads","errorCode":null,"errorMessage":"Expected worker to be run in node:worker_threads","messagePattern":"Expected worker to be run in node:worker_threads","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"packages/vitest/src/runtime/workers/init-threads.ts","lineNumber":7,"sourceCode":"import type { WorkerGlobalState, WorkerSetupContext } from '../../types/worker'\nimport type { Traces } from '../../utils/traces'\nimport { isMainThread, parentPort } from 'node:worker_threads'\nimport { init } from './init'\n\nif (isMainThread || !parentPort) {\n  throw new Error('Expected worker to be run in node:worker_threads')\n}\n\nexport default function workerInit(options: {\n  runTests: (method: 'run' | 'collect', state: WorkerGlobalState, traces: Traces) => Promise<void>\n  setup?: (context: WorkerSetupContext) => void | Promise<() => Promise<unknown>>\n}): void {\n  const { runTests } = options\n\n  init({\n    post: response => parentPort!.postMessage(response),\n    on: callback => parentPort!.on('message', callback),\n    off: callback => parentPort!.off('message', callback),\n    teardown: () => parentPort!.removeAllListeners('message'),\n    runTests: async (state, traces) => runTests('run', state, traces),\n    collectTests: async (state, traces) => runTests('collect', state, traces),\n    setup: options.setup,\n  })\n}","sourceCodeStart":1,"sourceCodeEnd":25,"githubUrl":"https://github.com/vitest-dev/vitest/blob/d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09/packages/vitest/src/runtime/workers/init-threads.ts#L1-L25","documentation":"Module-level guard in init-threads.ts (line 6-8): the threads worker entrypoint requires both isMainThread === false and a non-null parentPort, i.e. it must run inside a node:worker_threads Worker. Importing it in the main thread or a child process throws at import time.","triggerScenarios":"Importing the threads worker entry (init-threads.ts or the built runThreadsTests file) from the main thread, a forked child_process, or any non-worker-thread context where isMainThread is true or parentPort is null.","commonSituations":"A custom pool pointing at the threads entry but spawning a fork; tests or scripts that import internal vitest worker modules; experimentation with the worker init code outside a Worker.","solutions":["Do not import init-threads directly; let Vitest spawn the worker via pool: 'threads'.","If building a custom pool, load the threads entry only inside `new Worker(...)`.","Switch to pool: 'forks' if you intended to use child processes."],"exampleFix":"// before: importing threads entry in main thread\nimport workerInit from 'vitest/dist/workers/threads.js'\n\n// after: configure vitest to spawn the worker\n// vitest.config.ts\nexport default defineConfig({ test: { pool: 'threads' } })","handlingStrategy":"validation","validationCode":"import { isMainThread, parentPort } from 'node:worker_threads'\n\nfunction assertWorkerThreadContext() {\n  if (isMainThread || !parentPort) {\n    throw new Error(\n      'init-threads must be loaded inside a Worker (worker_threads); do not import it from the main thread.'\n    )\n  }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Only load the threads worker entry inside `new Worker(...)`.","Keep pool configuration declarative via test.pool rather than manual imports.","Document clearly which entry files are process-entry-only when forking vitest internals."],"tags":["threads-pool","worker","worker-threads","import-guard"],"analyzedSha":"d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09","analyzedAt":"2026-08-03T20:23:56.861Z","schemaVersion":2}