n8n-io/n8n · error · Error
Telemetry tracer must implement startSpan() and startActiveS
Error message
Telemetry tracer must implement startSpan() and startActiveSpan().
What it means
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.
Source
Thrown at packages/@n8n/agents/src/sdk/telemetry.ts:136
return wrapped;
},
});
}
function isOpenTelemetryTracer(value: unknown): value is Tracer {
return (
value !== null &&
typeof value === 'object' &&
typeof Reflect.get(value, 'startSpan') === 'function' &&
typeof Reflect.get(value, 'startActiveSpan') === 'function'
);
}
async function createAiSdkOpenTelemetryIntegrationFactory(
tracer: OpaqueTracer,
): Promise<(metadata: Record<string, AttributeValue> | undefined) => AiSdkTelemetry> {
if (!isOpenTelemetryTracer(tracer)) {
throw new Error('Telemetry tracer must implement startSpan() and startActiveSpan().');
}
const { LegacyOpenTelemetry } = await import('@ai-sdk/otel');
return (metadata) =>
new LegacyOpenTelemetry({ tracer: createMetadataEnrichedTracer(tracer, metadata) });
}
/**
* Create an OTel tracer + provider by dynamically importing OTel packages.
* This keeps OTel as a true optional peer dependency — the packages are only
* loaded when .otlpEndpoint() is actually called.
*/
async function createOtlpTracer(endpoint: string): Promise<{
tracer: OpaqueTracer;
provider: OpaqueTracerProvider;
}> {
const { NodeTracerProvider } = (await import('@opentelemetry/sdk-trace-node')) as {
NodeTracerProvider: new (config?: {View on GitHub (pinned to 5ac6606e81)
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.
Example fix
// before
const provider = new NodeTracerProvider();
new Telemetry().tracer(provider).build(); // wrong: provider, not tracer
// after
const provider = new NodeTracerProvider();
const tracer = provider.getTracer('@n8n/agents');
new Telemetry().tracer(tracer).build(); Defensive patterns
Strategy: type-guard
Validate before calling
import type { Telemetry } from '@n8n/agents';
function isOpenTelemetryTracer(value: unknown): boolean {
return (
value !== null &&
typeof value === 'object' &&
typeof Reflect.get(value as object, 'startSpan') === 'function' &&
typeof Reflect.get(value as object, 'startActiveSpan') === 'function'
);
}
if (!isOpenTelemetryTracer(myTracer)) {
throw new Error('Expected an OTel Tracer with startSpan/startActiveSpan');
}
new Telemetry().tracer(myTracer); Type guard
function isOpenTelemetryTracer(value: unknown): value is { startSpan: Function; startActiveSpan: Function } {
return (
value !== null &&
typeof value === 'object' &&
typeof Reflect.get(value as object, 'startSpan') === 'function' &&
typeof Reflect.get(value as object, 'startActiveSpan') === 'function'
);
} Try / catch
try {
const built = await new Telemetry().tracer(candidate).build();
} catch (err) {
if (err instanceof Error && err.message.includes('startSpan() and startActiveSpan()')) {
// fall back to OTLP endpoint auto-construction
telemetry = new Telemetry().otlpEndpoint(process.env.OTLP_ENDPOINT!);
} else throw err;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Cannot set both .tracer() and .otlpEndpoint() — use one or t
- OpenTelemetry active span callback is required.
- Additional Model Request Fields must be a JSON object
- The value in the "Extra Body" field must be a JSON object
- Reflector field "${fieldName}" must contain only strings
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/5034530a50200dae.
Report an issue: GitHub.