vitejs/vite · error · Error

[module runner] HMR client was closed.

Error message

[module runner] HMR client was closed.

What it means

Thrown in the import.meta.hot property getter (inside directRequest) when module code accesses import.meta.hot after the runner has been closed. The close() method sets this.hmrClient = undefined, so any subsequent access to the hot context throws. Module code that lazily accesses import.meta.hot (e.g., in a callback or conditional) can hit this if the runner was closed between module evaluation and the hot access.

Source

Thrown at packages/vite/src/module-runner/runner.ts:392

    // disambiguate the `<UNIT>:/` on windows: see nodejs/node#31710
    const href = posixPathToFileHref(modulePath)
    const meta = await createImportMeta(modulePath)
    const exports = Object.create(null)
    Object.defineProperty(exports, Symbol.toStringTag, {
      value: 'Module',
      enumerable: false,
      configurable: false,
    })

    mod.exports = exports

    let hotContext: ViteHotContext | undefined
    if (this.hmrClient) {
      Object.defineProperty(meta, 'hot', {
        enumerable: true,
        get: () => {
          if (!this.hmrClient) {
            throw new Error(`[module runner] HMR client was closed.`)
          }
          this.debug?.('[module runner] creating hmr context for', mod.url)
          hotContext ||= new HMRContext(this.hmrClient, mod.url)
          return hotContext
        },
        set: (value) => {
          hotContext = value
        },
      })
    }

    const context: ModuleRunnerContext = {
      [ssrImportKey]: request,
      [ssrDynamicImportKey]: dynamicRequest,
      [ssrModuleExportsKey]: exports,
      [ssrExportAllKey]: (obj: any) => exportAll(exports, obj),
      [ssrExportNameKey]: (name, getter) =>
        Object.defineProperty(exports, name, {

View on GitHub (pinned to 89620f09af)

Solutions

  1. Ensure all timers, intervals, and event listeners that might access import.meta.hot are cleaned up before calling runner.close().
  2. Guard import.meta.hot access in module code: if (import.meta.hot) { ... } — though note this checks existence, not closure.
  3. Use runner.isClosed() checks in long-lived callbacks before performing HMR operations.
  4. Set hmr: false on the runner if you don't need HMR, which prevents the hot property from being defined at all.

Example fix

// before — unguarded hot access in a callback
setInterval(() => {
  import.meta.hot.dispose(() => cleanup())
}, 1000)
// after — guard access
setInterval(() => {
  if (!runner.isClosed() && import.meta.hot) {
    import.meta.hot.dispose(() => cleanup())
  }
}, 1000)
Defensive patterns

Strategy: validation

Validate before calling

// Guard HMR access in deferred callbacks
async function safeHotAccess(runner, fn) {
  if (runner.isClosed()) {
    console.warn('Runner closed, skipping HMR operation')
    return
  }
  return fn()
}

// usage: safeHotAccess(runner, () => import.meta.hot?.dispose(cleanup))

Type guard

function isHmrAvailable(runner: ModuleRunner): boolean {
  return !runner.isClosed() && !!runner.hmrClient
}

Try / catch

// In module code with deferred HMR access
function safeHotDispose(callback) {
  try {
    if (import.meta.hot) {
      import.meta.hot.dispose(callback)
    }
  } catch (e) {
    if (e.message.includes('HMR client was closed')) {
      // runner was closed; nothing to dispose
      return
    }
    throw e
  }
}

Prevention

When it happens

Trigger: Module code accesses import.meta.hot inside a deferred callback (event handler, setTimeout, etc.) that fires after runner.close(). The module was loaded successfully, HMR context was available during load, but by the time the callback runs, the runner is closed and hmrClient is undefined.

Common situations: SSR applications with HMR enabled where the server shuts down (closing the runner) but lingering timers, intervals, or event listeners in loaded modules still reference import.meta.hot. Vitest test environments where the runner is torn down but async callbacks in test modules haven't been cleaned up.

Related errors


AI-assisted analysis of vitejs/vite@89620f09af (2026-08-03). Data as JSON: /data/errors/5c15ed8c941e53e2.json. Report an issue: GitHub.