n8n-io/n8n · error · Error
Cannot set both .tracer() and .otlpEndpoint() — use one or t
Error message
Cannot set both .tracer() and .otlpEndpoint() — use one or the other.
What it means
The Telemetry builder offers two mutually exclusive ways to source a tracer: a pre-built tracer via .tracer() or an OTLP HTTP endpoint URL via .otlpEndpoint() (which auto-constructs the tracer/provider). Setting both creates an ambiguity about which tracer the agent should use, so build() rejects the combination up front rather than silently preferring one.
Source
Thrown at packages/@n8n/agents/src/sdk/telemetry.ts:329
return this;
}
/**
* Set an OTLP endpoint to auto-create a tracer + provider.
* Requires `@opentelemetry/sdk-trace-node`, `@opentelemetry/exporter-trace-otlp-http`,
* and `@opentelemetry/sdk-trace-base` as peer dependencies.
*
* Mutually exclusive with `.tracer()`.
*/
otlpEndpoint(value: string): this {
this.otlpEndpointValue = value;
return this;
}
/** Build the telemetry configuration. */
async build(): Promise<BuiltTelemetry> {
if (this.tracerValue !== undefined && this.otlpEndpointValue !== undefined) {
throw new Error('Cannot set both .tracer() and .otlpEndpoint() — use one or the other.');
}
let tracer: OpaqueTracer = this.tracerValue;
let provider: OpaqueTracerProvider | undefined;
if (this.otlpEndpointValue !== undefined) {
const otlp = await createOtlpTracer(this.otlpEndpointValue);
tracer = otlp.tracer;
provider = otlp.provider;
}
const redactFn = this.redactFn;
const customIntegrations = redactFn
? this.integrationsList.map((integration) =>
wrapIntegrationWithRedaction(integration, redactFn),
)
: [...this.integrationsList];
let resolveIntegrations: BuiltTelemetry['resolveIntegrations'];View on GitHub (pinned to 5ac6606e81)
Solutions
- Pick one source: if you have a pre-built tracer (e.g. from LangSmithTelemetry or a shared provider), keep .tracer() and remove .otlpEndpoint().
- If you only have an OTLP URL, keep .otlpEndpoint() and remove the .tracer() call.
- Audit the builder chain for both method calls and delete the one not matching your infrastructure.
- If config is conditional, guard with an if/else so only one of the two methods runs per build.
Example fix
// before
new Telemetry()
.tracer(myTracer)
.otlpEndpoint('http://collector:4318') // throws on build
// after — choose one
new Telemetry().tracer(myTracer);
// or
new Telemetry().otlpEndpoint('http://collector:4318'); Defensive patterns
Strategy: validation
Validate before calling
const telemetry = new Telemetry();
if (process.env.OTLP_ENDPOINT) {
telemetry.otlpEndpoint(process.env.OTLP_ENDPOINT);
} else if (sharedTracer) {
telemetry.tracer(sharedTracer);
}
// never set both — this if/else guarantees mutual exclusion Type guard
function hasBothTracerSources(b: { tracerValue?: unknown; otlpEndpointValue?: unknown }): boolean {
return b.tracerValue !== undefined && b.otlpEndpointValue !== undefined;
} Try / catch
try {
await telemetry.build();
} catch (err) {
if (err instanceof Error && err.message.includes('.tracer() and .otlpEndpoint()')) {
// decide which to keep based on infra, clear the other, rebuild
} else throw err;
} Prevention
- Drive tracer-source selection from a single config flag (e.g. presence of OTLP_ENDPOINT env var) so both calls can never fire.
- Avoid chaining both methods conditionally without an else.
- Review telemetry setup during code review specifically for this mutual-exclusion rule.
When it happens
Trigger: Calling both new Telemetry().tracer(myTracer) and .otlpEndpoint('http://...') on the same builder instance, then awaiting .build() or passing the builder to an Agent which triggers the build.
Common situations: Copy-pasting telemetry config from two examples; refactoring from .tracer() to .otlpEndpoint() (or vice versa) without removing the prior call; merging config from a base profile that sets one and an override that sets the other.
Related errors
- Telemetry tracer must implement startSpan() and startActiveS
- OpenTelemetry active span callback is required.
- Model ID is required
- Invalid delegate sub-agent tool name "${name}": must start w
- ${toolName} requires resumeSubAgent and cancelSubAgent to be
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/ec60cdd048323052.
Report an issue: GitHub.