n8n-io/n8n · warning · Error

LangSmithTelemetry creates its own tracer — do not use .otlp

Error message

LangSmithTelemetry creates its own tracer — do not use .otlpEndpoint().

What it means

Thrown by the HTTP Request validator when a sensitive header (Authorization, X-API-Key, X-Auth-Token, X-Access-Token, API-Key, APIKey — matched case-insensitively) is set to a literal value rather than an n8n expression or credential. The validator wants secrets to live in the credential system so they never get serialized into the workflow JSON. A hardcoded secret here is a leak risk and also blocks proper credential reuse.

Source

Thrown at packages/@n8n/agents/src/integrations/langsmith.ts:372

 *
 * const agent = new Agent('assistant')
 *   .model('anthropic/claude-sonnet-4-5')
 *   .telemetry(telemetry)
 *   .instructions('...');
 * ```
 */
export class LangSmithTelemetry extends Telemetry {
	private langsmithConfig?: LangSmithTelemetryConfig;

	constructor(config?: LangSmithTelemetryConfig) {
		super();
		this.langsmithConfig = config;
	}

	/** @override Build telemetry config, creating the LangSmith tracer. */
	override async build(): Promise<BuiltTelemetry> {
		if (this.otlpEndpointValue !== undefined) {
			throw new Error('LangSmithTelemetry creates its own tracer — do not use .otlpEndpoint().');
		}

		// Clear any tracer from a previous build() so the parent's
		// .tracer()/.otlpEndpoint() mutual-exclusion check passes cleanly.
		this.tracerValue = undefined;

		// The LangSmith exporter silently drops all spans unless this is set.
		// Auto-enable it so users don't have to remember a magic env var.
		process.env.LANGCHAIN_TRACING_V2 ??= 'true';

		const { tracer, provider } = await createLangSmithTracer(
			this.langsmithConfig,
			this.resolvedKey,
		);
		this.tracerValue = tracer;

		// Call parent build() which handles integrations, redaction, etc.
		const built = await super.build();

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Move the secret into a credential: create an httpHeaderAuth or httpBearerAuth credential and reference it from the node instead of the header parameter.
  2. If the provider uses 'Authorization: Bearer <token>', create a new credential with genericAuthType='httpTemplatedCustomAuth' and template {"headers":{"Authorization":"Bearer {{api_key}}"}}.
  3. If you must keep the header inline for a throwaway test, wrap the value in an expression so it is not treated as a hardcoded literal (e.g. value: expr('<token>')) — but prefer a credential for anything persisted.
  4. Reuse an existing httpHeaderAuth/httpBearerAuth credential id instead of creating a new one.

Example fix

// before
httpRequest({
  name: 'Get Data',
  headerParameters: { parameters: [{ name: 'Authorization', value: 'Bearer abc123secret' }] },
});

// after — reuse a header auth credential
httpRequest({
  name: 'Get Data',
  authentication: 'genericCredentialType',
  genericAuthType: 'httpHeaderAuth',
  credentials: { httpHeaderAuth: { id: 'EXISTING_CRED_ID' } },
});
Defensive patterns

Strategy: validation

Validate before calling

import { isSensitiveHeader, containsExpression } from './validation-helpers';

function findHardcodedSensitiveHeaders(headerParameters: { parameters?: Array<{ name?: string; value?: unknown }> } | undefined): string[] {
  const offenders: string[] = [];
  for (const h of headerParameters?.parameters ?? []) {
    const valueStr = typeof h.value === 'string' ? h.value : JSON.stringify(h.value);
    if (h.name && isSensitiveHeader(h.name) && h.value && !containsExpression(valueStr)) {
      offenders.push(h.name);
    }
  }
  return offenders;
}

// before building the node:
const bad = findHardcodedSensitiveHeaders(params.headerParameters);
if (bad.length) throw new Error(`Move these headers into a credential: ${bad.join(', ')}`);

Type guard

function isExpressionValue(value: unknown): boolean {
  return typeof value === 'string' && (value.startsWith('=') || value.includes('={{'));
}

Prevention

When it happens

Trigger: A node of type n8n-nodes-base.httpRequest whose parameters.headerParameters.parameters[] contains an entry where header.name lowercases to one of the sensitive set, header.value is truthy, and the value string neither starts with '=' nor contains '={{'. The exact gate is isSensitiveHeader(name) && value && !containsExpression(value).

Common situations: An LLM/AI builder pastes a curl-derived 'Authorization: Bearer <real token>' straight into headerParameters; copying vendor docs that show the API key inline; testing with a real key and forgetting to swap it for a credential reference.

Related errors


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