{"id":"eba25364fc4bd3d2","repo":"vitest-dev/vitest","slug":"failed-to-import-custom-opentelemetry-sdk-script","errorCode":null,"errorMessage":"Failed to import custom OpenTelemetry SDK script (${options.sdkPath}): ${cause.message}","messagePattern":"Failed to import custom OpenTelemetry SDK script \\((.+?)\\): (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/vitest/src/utils/traces.ts","lineNumber":68,"sourceCode":"  #initRecorded = false\n\n  constructor(options: TracesOptions) {\n    if (options.enabled) {\n      const apiInit = import('@opentelemetry/api').then((api) => {\n        const otel = {\n          tracer: api.trace.getTracer(options.tracerName || 'vitest'),\n          context: api.context,\n          propagation: api.propagation,\n          trace: api.trace,\n          SpanKind: api.SpanKind,\n          SpanStatusCode: api.SpanStatusCode,\n        }\n        this.#otel = otel\n      }).catch(() => {\n        throw new Error(`\"@opentelemetry/api\" is not installed locally. Make sure you have setup OpenTelemetry instrumentation: https://vitest.dev/guide/open-telemetry`)\n      })\n      const sdkInit = (options.sdkPath ? import(/* @vite-ignore */ options.sdkPath!) : Promise.resolve()).catch((cause) => {\n        throw new Error(`Failed to import custom OpenTelemetry SDK script (${options.sdkPath}): ${cause.message}`)\n      })\n      this.#init = Promise.all([sdkInit, apiInit]).then(([sdk]) => {\n        if (sdk != null) {\n          if (sdk.default != null && typeof sdk.default === 'object' && typeof sdk.default.shutdown === 'function') {\n            this.#sdk = sdk.default\n          }\n          else if (options.watchMode !== true && process.env.VITEST_MODE !== 'watch') {\n            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.`)\n          }\n        }\n      }).finally(() => {\n        this.#initEndTime = performance.now()\n        this.#init = null\n      })\n    }\n  }\n\n  public isEnabled(): boolean {","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/vitest-dev/vitest/blob/d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09/packages/vitest/src/utils/traces.ts#L50-L86","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","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.","Install the OpenTelemetry packages the SDK module imports (`npm i @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node @opentelemetry/exporter-trace-otlp-prope`).","If you must use TypeScript, enable Node's native type stripping (Node >= 22.6 with --experimental-strip-types) or precompile the SDK to .js.","Read the `${cause.message}` portion of the error — it is the raw import failure (MODULE_NOT_FOUND, SyntaxError, etc.) and names the exact culprit."],"exampleFix":"// before (vitest.config.ts)\nexport default defineConfig({\n  test: { experimental: { openTelemetry: { enabled: true, sdkPath: './otel.ts' } } },\n})\n// after — ship a plain ESM .js file Node can import as-is\nexport default defineConfig({\n  test: { experimental: { openTelemetry: { enabled: true, sdkPath: './otel.js' } } },\n})\n// otel.js\nimport { NodeSDK } from '@opentelemetry/sdk-node'\nconst sdk = new NodeSDK({ /* ... */ })\nsdk.start()\nexport default sdk","handlingStrategy":"validation","validationCode":"// Run BEFORE launching vitest with tracing enabled — verifies the SDK\n// module is importable from the resolved project root.\nimport { pathToFileURL } from 'node:url'\nimport { resolve } from 'node:path'\n\nasync function assertOtelSdkLoads(root, sdkPath) {\n  if (!sdkPath) return\n  const abs = resolve(root, sdkPath)\n  const url = pathToFileURL(abs).toString()\n  try {\n    await import(url) // mirrors traces.ts:67 dynamic import\n  } catch (cause) {\n    throw new Error(\n      `experimental.openTelemetry.sdkPath (${sdkPath}) would fail to import: ${cause.message}`,\n    )\n  }\n}\n// await assertOtelSdkLoads(process.cwd(), './otel.js')","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Pre-author the SDK as a plain .js ESM file; avoid relying on Vite to transform it (it won't — the file is imported by Node directly).","Smoke-test the module standalone with `node ./path/to/otel.js` from project root before wiring it into vitest.config.","Keep the sdkPath relative to the configured `root` and re-verify it after moving files or changing roots in a monorepo.","Pin the OpenTelemetry packages in package.json so a missing/changed peer dependency cannot break the SDK import silently."],"tags":["opentelemetry","tracing","config","esm","dynamic-import"],"analyzedSha":"d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09","analyzedAt":"2026-08-03T20:23:56.861Z","schemaVersion":2}