vitest-dev/vitest · error · Error

"@opentelemetry/api" is not installed locally. Make sure…

Error message

"@opentelemetry/api" is not installed locally. Make sure you have setup OpenTelemetry instrumentation: https://vitest.dev/guide/open-telemetry

What it means

When tracing is enabled, the Traces constructor dynamically imports @opentelemetry/api. Vitest does not bundle OpenTelemetry; it expects the package to be resolvable from the user's project. If the import rejects (module not found), the .catch at traces.ts:64-66 rethrows this error with a link to the setup guide. The rejection is deferred because it lives in a promise chain, so it surfaces when waitInit() is awaited or the init promise settles.

Solutions

  1. Install the package: npm i @opentelemetry/api (or the equivalent pnpm/yarn command).
  2. Verify resolution from the same CWD Vitest runs in: node -e "require.resolve('@opentelemetry/api')".
  3. Disable tracing (remove --experimental-traces / set experimentalTraces.enabled to false) if you do not need it.
  4. In a monorepo, hoist the dependency or add it to the workspace where Vitest executes.

Example fix

# before
vitest --experimental-traces   # throws

# after
npm i @opentelemetry/api
vitest --experimental-traces
Defensive patterns

Strategy: validation

Validate before calling

function canResolveOtelApi() {
  try {
    require.resolve('@opentelemetry/api')
    return true
  } catch {
    return false
  }
}
if (config.experimentalTraces?.enabled && !canResolveOtelApi()) {
  console.warn('Tracing requested but @opentelemetry/api is missing; disabling.')
  config.experimentalTraces.enabled = false
}

Try / catch

try {
  await traces.waitInit()
} catch (e) {
  if (e instanceof Error && /not installed locally/.test(e.message)) {
    console.warn('OpenTelemetry tracing unavailable:', e.message)
  } else {
    throw e
  }
}

Prevention

When it happens

Trigger: Enabling tracing via the experimentalTraces option (or --experimental-traces CLI flag) while @opentelemetry/api is not resolvable from the Vitest process. The constructor only attempts the import when options.enabled is true.

Common situations: Forgot to run npm i @opentelemetry/api; CI install step pruned dev dependencies; pnpm hoisting or monorepo isolation hides the package from the Vitest CWD; upgrading Vitest to a version that introduced tracing without updating the project deps.

Related errors


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

Appendix: source

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

  #noopContext = createNoopContext()
  #initStartTime = performance.now()
  #initEndTime = 0
  #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
      })
    }

View on GitHub (pinned to 1fa9837ec2)