{"record":{"id":"f15a9abd2cfe6424","repo":"vitejs/vite","slug":"vite-module-runner-has-been-closed","errorCode":null,"errorMessage":"Vite module runner has been closed.","messagePattern":"Vite module runner has been closed\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/vite/src/module-runner/runner.ts","lineNumber":273,"sourceCode":"              builtin,\n        )\n        this.isBuiltin = createIsBuiltin(builtins)\n        this.debug?.('[module runner] builtins loaded:', builtins)\n      } finally {\n        this.builtinsPromise = undefined\n      }\n    })()\n\n    return this.builtinsPromise\n  }\n\n  private async getModuleInformation(\n    url: string,\n    importer: string | undefined,\n    cachedModule: EvaluatedModuleNode | undefined,\n  ): Promise<EvaluatedModuleNode> {\n    if (this.closed) {\n      throw new Error(`Vite module runner has been closed.`)\n    }\n\n    await this.ensureBuiltins()\n\n    this.debug?.('[module runner] fetching', url)\n\n    const isCached = !!(typeof cachedModule === 'object' && cachedModule.meta)\n\n    const fetchedModule = // fast return for established externalized pattern\n      (\n        url.startsWith('data:') || this.isBuiltin?.(url)\n          ? { externalize: url, type: 'builtin' }\n          : await this.transport.invoke('fetchModule', [\n              url,\n              importer,\n              {\n                cached: isCached,\n                startOffset: this.evaluator.startOffset,","sourceCodeStart":255,"sourceCodeEnd":291,"githubUrl":"https://github.com/vitejs/vite/blob/b4d66fee14d970f45b8a6f3d7d6aee73ca9b88ab/packages/vite/src/module-runner/runner.ts#L255-L291","documentation":"`getModuleInformation` is the entry to module fetching; it first checks the runner's `closed` flag and throws if `close()` was called. After close, the runner's caches are cleared and the transport is disconnected, so no further imports are valid.","triggerScenarios":"Calling `runner.import(url)` (or any operation that triggers `cachedModule`/`getModuleInformation`) after `await runner.close()` has resolved.","commonSituations":"Teardown races in tests where a module import is in-flight or queued after the runner is closed. Shared runner instances being closed by one consumer while another still imports. Long-running servers that close the runner during shutdown but still service requests.","solutions":["Guard imports with `runner.isClosed()` before calling `import`.","Ensure no concurrent work references the runner after `close()`; await all in-flight imports before closing.","Create a fresh runner if you need to import again after closing."],"exampleFix":"// before\nawait runner.close()\nawait runner.import('/src/app.ts') // throws\n\n// after\nif (!runner.isClosed()) {\n  await runner.import('/src/app.ts')\n}","handlingStrategy":"try-catch","validationCode":"async function safeImport(runner: { isClosed: () => boolean; import: (u: string) => Promise<unknown> }, url: string) {\n  if (runner.isClosed()) throw new Error(`Refusing to import ${url}: runner is closed`)\n  return runner.import(url)\n}","typeGuard":"function runnerIsAlive(runner: { isClosed: () => boolean }): boolean {\n  return !runner.isClosed()\n}","tryCatchPattern":"try {\n  return await runner.import(url)\n} catch (e) {\n  if (e instanceof Error && /module runner has been closed/.test(e.message)) {\n    // re-create runner or skip - do not retry on the closed instance\n    return null\n  }\n  throw e\n}","preventionTips":["Await all in-flight imports before calling runner.close().","Gate further imports behind runner.isClosed() in long-lived services."],"tags":["module-runner","lifecycle","close","runtime"],"backgroundTag":null,"analyzedSha":"b4d66fee14d970f45b8a6f3d7d6aee73ca9b88ab","analyzedAt":"2026-08-11T11:49:19.515Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}