vitest-dev/vitest · error

"Istanbul" coverage provider is not compatible with…

Error message

"Istanbul" coverage provider is not compatible with "experimental.viteModuleRunner: false". Please, enable "viteModuleRunner" or switch to "v8" coverage provider.

What it means

The Istanbul coverage provider instruments code through its own transform pipeline and depends on Vitest's Vite module runner to load the instrumented output. Disabling the experimental module runner (`experimental.viteModuleRunner: false`) breaks that pipeline, so Vitest refuses the pairing rather than emitting silently-wrong coverage.

Solutions

  1. Switch coverage to `provider: 'v8'`.
  2. Or remove `experimental.viteModuleRunner: false` (let it default / set it true) and keep Istanbul.
  3. If you must disable the runner for other reasons, accept v8 coverage for those runs.

Example fix

// before
test: {
  coverage: { provider: 'istanbul' },
  experimental: { viteModuleRunner: false },
}
// after
test: {
  coverage: { provider: 'v8' },
  experimental: { viteModuleRunner: false },
}
Defensive patterns

Strategy: validation

Validate before calling

if (config.test?.coverage?.provider === 'istanbul' && config.test?.experimental?.viteModuleRunner === false) {
  throw new Error('Istanbul coverage requires viteModuleRunner; switching to v8.')
}
config.test.coverage.provider = 'v8'

Type guard

function isCompatibleCoverageCombo(provider: string, runner?: boolean): boolean {
  return !(provider === 'istanbul' && runner === false)
}

Prevention

When it happens

Trigger: Setting `coverage.provider: 'istanbul'` together with `experimental.viteModuleRunner: false` in the same config.

Common situations: Opting out of the experimental runner for a stability reason while leaving Istanbul as the default coverage provider.

Related errors


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

Appendix: source

Thrown at packages/vitest/src/node/config/resolveConfig.ts:480

      // if `instances` were defined, but now they are empty,
      // let's throw an error because the filter is invalid
      if (!browser.instances.length) {
        throw new Error([
          `"browser.instances" was set in the config, but the array is empty. Define at least one browser config.`,
          ` The "browser.name" was set to "${browser.name}" which filtered all configs (${instances.map(c => c.browser).join(', ')}). Did you mean to use another name?`,
        ].join(''))
      }
    }

    browser.instances.forEach((instance) => {
      instance.name ??= resolved.name
        ? `${resolved.name} (${instance.browser})`
        : instance.browser
    })
  }

  if (resolved.coverage.enabled && resolved.coverage.provider === 'istanbul' && resolved.experimental?.viteModuleRunner === false) {
    throw new Error(`"Istanbul" coverage provider is not compatible with "experimental.viteModuleRunner: false". Please, enable "viteModuleRunner" or switch to "v8" coverage provider.`)
  }

  if (browser.enabled && resolved.detectAsyncLeaks) {
    logger.console.warn(c.yellow('The option "detectAsyncLeaks" is not supported in browser mode and will be ignored.'))
  }

  resolved.coverage.reporter = resolveCoverageReporters(resolved.coverage.reporter)
  if (isAgent) {
    // default to `skipFull` and add `text-summary` reporter when `text` reporter is used on agents
    const text = resolved.coverage.reporter.find(([name]) => name === 'text')
    const textSummary = resolved.coverage.reporter.find(([name]) => name === 'text-summary')
    if (text) {
      (text as any)[1] = { skipFull: true, ...text[1] as any }
      if (!textSummary) {
        resolved.coverage.reporter.push(['text-summary', {}])
      }
    }
  }

View on GitHub (pinned to 1fa9837ec2)