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

  1. Pick one source: if you have a pre-built tracer (e.g. from LangSmithTelemetry or a shared provider), keep .tracer() and remove .otlpEndpoint().
  2. If you only have an OTLP URL, keep .otlpEndpoint() and remove the .tracer() call.
  3. Audit the builder chain for both method calls and delete the one not matching your infrastructure.
  4. 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

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


AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12). Data as JSON: /api/errors/ec60cdd048323052. Report an issue: GitHub.