{"record":{"id":"5034530a50200dae","repo":"n8n-io/n8n","slug":"telemetry-tracer-must-implement-startspan-and-st","errorCode":null,"errorMessage":"Telemetry tracer must implement startSpan() and startActiveSpan().","messagePattern":"Telemetry tracer must implement startSpan\\(\\) and startActiveSpan\\(\\)\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/@n8n/agents/src/sdk/telemetry.ts","lineNumber":136,"sourceCode":"\t\t\treturn wrapped;\n\t\t},\n\t});\n}\n\nfunction isOpenTelemetryTracer(value: unknown): value is Tracer {\n\treturn (\n\t\tvalue !== null &&\n\t\ttypeof value === 'object' &&\n\t\ttypeof Reflect.get(value, 'startSpan') === 'function' &&\n\t\ttypeof Reflect.get(value, 'startActiveSpan') === 'function'\n\t);\n}\n\nasync function createAiSdkOpenTelemetryIntegrationFactory(\n\ttracer: OpaqueTracer,\n): Promise<(metadata: Record<string, AttributeValue> | undefined) => AiSdkTelemetry> {\n\tif (!isOpenTelemetryTracer(tracer)) {\n\t\tthrow new Error('Telemetry tracer must implement startSpan() and startActiveSpan().');\n\t}\n\n\tconst { LegacyOpenTelemetry } = await import('@ai-sdk/otel');\n\treturn (metadata) =>\n\t\tnew LegacyOpenTelemetry({ tracer: createMetadataEnrichedTracer(tracer, metadata) });\n}\n\n/**\n * Create an OTel tracer + provider by dynamically importing OTel packages.\n * This keeps OTel as a true optional peer dependency — the packages are only\n * loaded when .otlpEndpoint() is actually called.\n */\nasync function createOtlpTracer(endpoint: string): Promise<{\n\ttracer: OpaqueTracer;\n\tprovider: OpaqueTracerProvider;\n}> {\n\tconst { NodeTracerProvider } = (await import('@opentelemetry/sdk-trace-node')) as {\n\t\tNodeTracerProvider: new (config?: {","sourceCodeStart":118,"sourceCodeEnd":154,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/agents/src/sdk/telemetry.ts#L118-L154","documentation":"The Telemetry builder accepts a custom tracer via .tracer(), but it must be structurally compatible with the OpenTelemetry Tracer interface. The runtime duck-types the object via Reflect.get and requires both `startSpan` and `startActiveSpan` to be functions before it can wrap it with @ai-sdk/otel's LegacyOpenTelemetry integration. Any object missing either method is rejected at build time rather than silently producing an uninstrumented agent.","triggerScenarios":"Calling new Telemetry().tracer(someObject).build() (or passing the builder to an Agent which lazy-builds) where someObject lacks startSpan or startActiveSpan. Commonly hit by passing a TracerProvider instead of the Tracer returned by provider.getTracer(...), by passing a NoopTracer stub, or by passing a mock tracer in tests that only implements one method.","commonSituations":"Confusing the OTel TracerProvider with the Tracer (the provider's getTracer() returns the tracer); wiring a LangSmith or custom observability shim that exposes a different span API; version drift where @opentelemetry/api's Tracer interface changed; passing a partial mock during unit testing.","solutions":["Pass the Tracer, not the TracerProvider — call provider.getTracer('my-name') and pass that result to .tracer().","If you only have an OTLP endpoint URL, drop .tracer() and use .otlpEndpoint(url) instead; the builder will construct a compliant tracer for you.","Verify the object structurally: ensure both obj.startSpan and obj.startActiveSpan are functions before calling .tracer(obj).","In tests, build a minimal tracer stub that implements both startSpan() and startActiveSpan() returning no-op span objects."],"exampleFix":"// before\nconst provider = new NodeTracerProvider();\nnew Telemetry().tracer(provider).build(); // wrong: provider, not tracer\n\n// after\nconst provider = new NodeTracerProvider();\nconst tracer = provider.getTracer('@n8n/agents');\nnew Telemetry().tracer(tracer).build();","handlingStrategy":"type-guard","validationCode":"import type { Telemetry } from '@n8n/agents';\n\nfunction isOpenTelemetryTracer(value: unknown): boolean {\n  return (\n    value !== null &&\n    typeof value === 'object' &&\n    typeof Reflect.get(value as object, 'startSpan') === 'function' &&\n    typeof Reflect.get(value as object, 'startActiveSpan') === 'function'\n  );\n}\n\nif (!isOpenTelemetryTracer(myTracer)) {\n  throw new Error('Expected an OTel Tracer with startSpan/startActiveSpan');\n}\nnew Telemetry().tracer(myTracer);","typeGuard":"function isOpenTelemetryTracer(value: unknown): value is { startSpan: Function; startActiveSpan: Function } {\n  return (\n    value !== null &&\n    typeof value === 'object' &&\n    typeof Reflect.get(value as object, 'startSpan') === 'function' &&\n    typeof Reflect.get(value as object, 'startActiveSpan') === 'function'\n  );\n}","tryCatchPattern":"try {\n  const built = await new Telemetry().tracer(candidate).build();\n} catch (err) {\n  if (err instanceof Error && err.message.includes('startSpan() and startActiveSpan()')) {\n    // fall back to OTLP endpoint auto-construction\n    telemetry = new Telemetry().otlpEndpoint(process.env.OTLP_ENDPOINT!);\n  } else throw err;\n}","preventionTips":["Always resolve tracer via provider.getTracer(name) — never pass the provider itself.","Centralize telemetry construction in one factory function so the shape is validated in a single place.","For tests, maintain a reusable stub tracer that implements both required methods."],"tags":["telemetry","opentelemetry","configuration","type-mismatch"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}