{"id":"1ec775041a810130","repo":"vitest-dev/vitest","slug":"vitest-pool-runner-cannot-start-a-stopped-runne","errorCode":null,"errorMessage":"[vitest-pool-runner]: Cannot start a stopped runner","messagePattern":"\\[vitest-pool-runner\\]: Cannot start a stopped runner","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/vitest/src/node/pools/poolRunner.ts","lineNumber":180,"sourceCode":"  private getOTELCarrier() {\n    const activeContext = this._otel?.currentContext || this._otel?.workerContext\n    return activeContext\n      ? this._traces.getContextCarrier(activeContext)\n      : undefined\n  }\n\n  async start(options: { workerId: number }): Promise<void> {\n    // Wait for any ongoing operation to complete\n    if (this._operationLock) {\n      await this._operationLock\n    }\n\n    if (this._state === RunnerState.STARTED || this._state === RunnerState.STARTING) {\n      return\n    }\n\n    if (this._state === RunnerState.STOPPED) {\n      throw new Error('[vitest-pool-runner]: Cannot start a stopped runner')\n    }\n\n    // Create operation lock to prevent concurrent start/stop\n    this._operationLock = createDefer()\n\n    let startSpan: Span | undefined\n    try {\n      this._state = RunnerState.STARTING\n\n      await this._traces.$(\n        `vitest.${this.worker.name}.start`,\n        { context: this._otel?.workerContext },\n        () => this.worker.start(),\n      )\n\n      // Attach event listeners AFTER starting worker to avoid issues\n      // if worker.start() fails\n      this.worker.on('error', this.emitWorkerError)","sourceCodeStart":162,"sourceCodeEnd":198,"githubUrl":"https://github.com/vitest-dev/vitest/blob/d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09/packages/vitest/src/node/pools/poolRunner.ts#L162-L198","documentation":"Thrown by PoolRunner.start when the runner's state is already STOPPED. A runner that has been stopped has torn down its worker process/thread and closed its RPC channel, so it cannot be restarted; Vitest always creates a new PoolRunner for a fresh task rather than reusing a stopped one.","triggerScenarios":"runner.start() is called after runner.stop() completed (state === STOPPED), at packages/vitest/src/node/pools/poolRunner.ts:179-180. Normally Pool.getPoolRunner constructs a new runner for stopped workers, so this implies reuse of a stopped instance.","commonSituations":"An internal bug where a stopped runner is retained in sharedRunners and picked up again; programmatic misuse of the @experimental PoolRunner API calling start() after stop(); a cancellation/teardown race that re-schedules onto a stopped runner.","solutions":["Report as a Vitest bug if seen during normal CLI usage (this is an internal invariant).","If using PoolRunner directly (experimental API), never call start() after stop(); construct a new PoolRunner instead.","Retry — transient teardown races during cancellation can trigger this once."],"exampleFix":"// before (experimental API misuse): reusing a stopped runner\nawait runner.stop()\nawait runner.start({ workerId: 1 }) // throws\n\n// after: construct a new runner\nawait runner.stop()\nconst fresh = new PoolRunner(options, new ForksPoolWorker(options))\nawait fresh.start({ workerId: 1 })","handlingStrategy":"validation","validationCode":"// Experimental PoolRunner API: never start a stopped runner\nfunction assertCanStart(runner) {\n  if (runner.isTerminated) {\n    throw new Error('Runner is stopped; create a new PoolRunner instead of restarting.')\n  }\n}","typeGuard":"function isReusableRunner(runner) {\n  return !runner.isTerminated\n}","tryCatchPattern":"try {\n  await runner.start({ workerId: 1 })\n} catch (e) {\n  if (e.message === '[vitest-pool-runner]: Cannot start a stopped runner') {\n    // construct a fresh runner instead of retrying\n    throw new Error('Runner was stopped; recreate it before starting')\n  }\n  throw e\n}","preventionTips":["Never call start() after stop() on a PoolRunner; always construct a new one.","Treat PoolRunner as @experimental and avoid direct lifecycle manipulation.","Report internal cases seen during normal CLI usage as bugs."],"tags":["pool-runner","internal-bug","lifecycle","experimental-api","assertion"],"analyzedSha":"d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09","analyzedAt":"2026-08-03T20:23:56.861Z","schemaVersion":2}