vitest-dev/vitest · error · Error

[vitest] Expected synchronous evaluation to complete for ${r

Error message

[vitest] Expected synchronous evaluation to complete for ${rootIdentifier}, but module status is "${root.module.status}". This is a bug in Vitest.

What it means

Vitest's vm ESM executor asserts that a module graph containing no top-level await evaluates synchronously. After calling root.module.evaluate() it checks status: 'errored' rethrows the error, 'evaluated' is success, and any other status (e.g. 'evaluating', 'unlinked') trips this assertion. The message explicitly states it is a bug in Vitest, because with no async in the graph Node's vm SourceTextModule.evaluate() is contractually synchronous.

Source

Thrown at packages/vitest/src/runtime/vm/esm-executor.ts:302

          : `its dependency uses top-level await (${culprit})`,
      )
    }

    for (const [identifier, entry] of scratch) {
      if (entry.commit && !this.moduleCache.has(identifier)) {
        this.moduleCache.set(identifier, entry.module)
      }
    }

    // with no top-level await in the graph, evaluate() fulfills synchronously
    // and an evaluation error lands on `status`/`error`, not on the promise
    root.module.evaluate().catch(() => {})

    if (root.module.status === 'errored') {
      throw root.module.error
    }
    if (root.module.status !== 'evaluated') {
      throw new Error(
        `[vitest] Expected synchronous evaluation to complete for ${rootIdentifier}, but module status is "${root.module.status}". This is a bug in Vitest.`,
      )
    }
    return root.module
  }

  // A cached module is reusable by the sync walker only when it is settled:
  // anything else (a pending Promise or a module in 'unlinked' → 'evaluating')
  // is a concurrent import() mid-flight that a synchronous require() can
  // neither await nor safely link against.
  private reuseSyncModule(
    identifier: string,
    cached: VMModule | Promise<VMModule>,
  ): VMModule {
    if (cached instanceof Promise) {
      throw createConcurrentRequireError(identifier)
    }
    if (cached.status === 'errored') {

View on GitHub (pinned to 1fa9837ec2)

Solutions

  1. Report it as a Vitest bug with the full module graph and the module that triggered the require(esm); include Vitest, Node, and OS versions.
  2. Check whether any dependency in the import graph newly introduced top-level await (e.g. after an npm update) and refactor or mock it.
  3. Try a different pool (threads/forks instead of vm) to confirm it is vm-executor specific: test.pool='threads'.
  4. Downgrade Node.js to a known-good LTS to rule out a vm.SourceTextModule regression, or downgrade Vitest to the last working version.

Example fix

// before: test file imports a dep that gained top-level await
import 'some-dep' // some-dep now has `await` at top level

// after: avoid the sync require path by using dynamic import, or pin the dep
import('some-dep')
Defensive patterns

Strategy: try-catch

Validate before calling

// Cannot be validated by callers reliably; it is an internal assertion.
// Best pre-check: confirm the graph is truly synchronous before require(esm).
const hasAsync = (m) => m instanceof SourceTextModule && m.hasAsyncGraph()
if (!hasAsync(root)) { /* safe to require */ }

Type guard

function isEvaluated(m) {
  return m.status === 'evaluated' || m.status === 'errored'
}

Try / catch

try {
  executor.requireSync(identifier)
} catch (e) {
  if (String(e.message).includes('This is a bug in Vitest')) {
    // file an issue; fall back to dynamic import
    await import(identifier)
  } else throw e
}

Prevention

When it happens

Trigger: A require(esm) import resolves a module whose hasAsyncGraph() reported false, yet root.module.evaluate() leaves status as anything other than 'errored' or 'evaluated'. Concretely this fires inside EsmExecutor when the sync module walker commits a graph it believed to be synchronous but Node disagrees, or when the module transitions states mid-evaluation due to re-entrant imports.

Common situations: Hitting an internal Vitest regression after upgrading Node.js (vm.SourceTextModule behavior changed), mixing require(esm) with modules that have hidden async (a dependency added top-level await), or a re-entrant dynamic import() racing the sync evaluation. Rare in stable releases; usually surfaces on nightly/edge Vitest or Node versions.

Related errors


AI-assisted analysis of vitest-dev/vitest@1fa9837ec2 (2026-08-11). Data as JSON: /api/errors/d75845866ef066e4. Report an issue: GitHub.