vitest-dev/vitest · error · Error

Failed to import custom OpenTelemetry SDK script (${options.

Error message

Failed to import custom OpenTelemetry SDK script (${options.sdkPath}): ${cause.message}

What it means

Vitest's Traces constructor (packages/vitest/src/utils/traces.ts:67-69) dynamically imports the module referenced by experimental.openTelemetry.sdkPath when tracing is enabled. The path is resolved relative to the project root and converted to a file:// URL in resolveConfig.ts:972-977; Vitest does NOT transform this file. If that dynamic import rejects (bad path, syntax error, missing dependency, untranspiled TypeScript), the rejection is caught and re-thrown as this wrapped Error with the underlying cause's message, aborting worker initialization.

Source

Thrown at packages/vitest/src/utils/traces.ts:68

  #initRecorded = false

  constructor(options: TracesOptions) {
    if (options.enabled) {
      const apiInit = import('@opentelemetry/api').then((api) => {
        const otel = {
          tracer: api.trace.getTracer(options.tracerName || 'vitest'),
          context: api.context,
          propagation: api.propagation,
          trace: api.trace,
          SpanKind: api.SpanKind,
          SpanStatusCode: api.SpanStatusCode,
        }
        this.#otel = otel
      }).catch(() => {
        throw new Error(`"@opentelemetry/api" is not installed locally. Make sure you have setup OpenTelemetry instrumentation: https://vitest.dev/guide/open-telemetry`)
      })
      const sdkInit = (options.sdkPath ? import(/* @vite-ignore */ options.sdkPath!) : Promise.resolve()).catch((cause) => {
        throw new Error(`Failed to import custom OpenTelemetry SDK script (${options.sdkPath}): ${cause.message}`)
      })
      this.#init = Promise.all([sdkInit, apiInit]).then(([sdk]) => {
        if (sdk != null) {
          if (sdk.default != null && typeof sdk.default === 'object' && typeof sdk.default.shutdown === 'function') {
            this.#sdk = sdk.default
          }
          else if (options.watchMode !== true && process.env.VITEST_MODE !== 'watch') {
            console.warn(`OpenTelemetry instrumentation module (${options.sdkPath}) does not have a default export with a "shutdown" method. Vitest won't be able to ensure that all traces are processed in time. Try running Vitest in watch mode instead.`)
          }
        }
      }).finally(() => {
        this.#initEndTime = performance.now()
        this.#init = null
      })
    }
  }

  public isEnabled(): boolean {

View on GitHub (pinned to d568f8ce37)

Solutions

  1. Verify the sdkPath file exists and resolves against the configured `root` (it is resolved with path.resolve(root, sdkPath) and turned into a file:// URL); fix the path or move the file.
  2. Use a .js (ESM) file for the SDK module, and confirm `node ./otel.js` imports it cleanly from the project root — any error there is the same error Vitest reports.
  3. Install the OpenTelemetry packages the SDK module imports (`npm i @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node @opentelemetry/exporter-trace-otlp-prope`).
  4. If you must use TypeScript, enable Node's native type stripping (Node >= 22.6 with --experimental-strip-types) or precompile the SDK to .js.
  5. Read the `${cause.message}` portion of the error — it is the raw import failure (MODULE_NOT_FOUND, SyntaxError, etc.) and names the exact culprit.

Example fix

// before (vitest.config.ts)
export default defineConfig({
  test: { experimental: { openTelemetry: { enabled: true, sdkPath: './otel.ts' } } },
})
// after — ship a plain ESM .js file Node can import as-is
export default defineConfig({
  test: { experimental: { openTelemetry: { enabled: true, sdkPath: './otel.js' } } },
})
// otel.js
import { NodeSDK } from '@opentelemetry/sdk-node'
const sdk = new NodeSDK({ /* ... */ })
sdk.start()
export default sdk
Defensive patterns

Strategy: validation

Validate before calling

// Run BEFORE launching vitest with tracing enabled — verifies the SDK
// module is importable from the resolved project root.
import { pathToFileURL } from 'node:url'
import { resolve } from 'node:path'

async function assertOtelSdkLoads(root, sdkPath) {
  if (!sdkPath) return
  const abs = resolve(root, sdkPath)
  const url = pathToFileURL(abs).toString()
  try {
    await import(url) // mirrors traces.ts:67 dynamic import
  } catch (cause) {
    throw new Error(
      `experimental.openTelemetry.sdkPath (${sdkPath}) would fail to import: ${cause.message}`,
    )
  }
}
// await assertOtelSdkLoads(process.cwd(), './otel.js')

Prevention

When it happens

Trigger: Setting `test.experimental.openTelemetry.enabled = true` together with a non-empty `experimental.openTelemetry.sdkPath` whose target cannot be imported by plain Node.js: a .ts file without Node's type-stripping (--experimental-strip-types), a path that does not exist relative to the resolved project root, a module that itself throws on load (e.g. missing '@opentelemetry/sdk-node' dependency), or a module using bare-import specifiers Node cannot resolve.

Common situations: Pointing sdkPath at a .ts instrumentation file while running on Node < 22.6 (no native type stripping); moving/renaming the otel.js file without updating the config; adding the SDK config before running `npm i @opentelemetry/sdk-node ...`; relative path that resolves against the wrong root (monorepo with multiple roots); ESM/CJS mismatch inside the SDK module; forgetting that the file is loaded as-is by Node, not processed by Vite.

Related errors


AI-assisted analysis of vitest-dev/vitest@d568f8ce37 (2026-08-03). Data as JSON: /data/errors/eba25364fc4bd3d2.json. Report an issue: GitHub.