JuliusBrussee/caveman · error

cavemanExporterConfig: protocol

Error message

cavemanExporterConfig: protocol ${protocol} is not accepted by ${baseUrl}/otlp/v1/traces; the Caveman OTLP endpoint decodes protobuf-JSON only. Use 'http/json'.

What it means

cavemanExporterConfig builds an OTLP trace exporter pointed at the Caveman gateway, whose /otlp/v1/traces endpoint decodes protobuf-JSON only. The protocol option exists mainly to catch configs copied from standard OpenTelemetry setups: anything other than 'http/json' (the default) is rejected up front, instead of failing later with opaque 4xx responses from the gateway.

Solutions

  1. Omit the protocol option entirely — it defaults to 'http/json'
  2. Or set protocol: 'http/json' explicitly
  3. Keep transport concerns in options.headers (custom auth headers), not protocol

Example fix

// before
const cfg = cavemanExporterConfig({ baseUrl, apiKey, projectId, environment, protocol: 'http/protobuf' });

// after
const cfg = cavemanExporterConfig({ baseUrl, apiKey, projectId, environment }); // defaults to 'http/json'
Defensive patterns

Strategy: type-guard

Type guard

type CavemanProtocol = 'http/json';

function isSupportedProtocol(value: unknown): value is CavemanProtocol {
  return value === undefined || value === 'http/json';
}

Prevention

When it happens

Trigger: cavemanExporterConfig({ baseUrl, apiKey, projectId, environment, protocol: 'grpc' }) or protocol: 'http/protobuf' — the values standard OTLP Node SDKs and collector configs use.

Common situations: Porting an existing OpenTelemetry config where http/protobuf or grpc is the norm; a version upgrade that added protocol validation to previously-silent misconfiguration; copy-paste from generic OTLP exporter docs.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@766dce6b13 (2026-08-18). Data as JSON: /api/errors/90e55afe4ca8fae0. Report an issue: GitHub.

Appendix: source

Thrown at packages/mastra/src/index.ts:93

 * Builds the OTLP settings a Mastra application passes to its telemetry config.
 *
 * The endpoint is Caveman's OTLP route (`POST {baseUrl}/otlp/v1/traces`). That
 * handler decodes protobuf-JSON, not binary protobuf, so `http/json` is the only
 * protocol it can accept — a protobuf or gRPC batch is rejected rather than
 * silently dropped here.
 *
 * Auth: the gateway reads `x-cave-api-key` first and falls back to
 * `authorization` (a `Bearer ` prefix is stripped before the key is parsed), so
 * both headers are emitted and either one alone would authenticate.
 */
export function cavemanExporterConfig(options: CavemanExporterOptions): CavemanExporterConfig {
  const baseUrl = requireBaseUrl(options.baseUrl, "cavemanExporterConfig: baseUrl");
  const apiKey = requireNonEmpty(options.apiKey, "cavemanExporterConfig: apiKey");
  const projectId = requireNonEmpty(options.projectId, "cavemanExporterConfig: projectId");
  const environment = requireNonEmpty(options.environment, "cavemanExporterConfig: environment");
  const protocol = options.protocol ?? "http/json";
  if (protocol !== "http/json") {
    throw new Error(
      `cavemanExporterConfig: protocol ${protocol} is not accepted by ${baseUrl}/otlp/v1/traces; ` +
        "the Caveman OTLP endpoint decodes protobuf-JSON only. Use 'http/json'.",
    );
  }

  const endpoint = `${baseUrl}/otlp/v1/traces`;
  const headers: Record<string, string> = {
    "x-cave-api-key": apiKey,
    authorization: `Bearer ${apiKey}`,
  };
  if (options.agentId) headers["x-cave-agent"] = options.agentId;
  Object.assign(headers, options.headers ?? {});

  // §23.3 of docs/self_learning_implementation_spec.md — the required metadata block.
  const resourceAttributes: Record<string, string> = {
    "caveman.project.id": projectId,
    "caveman.environment": environment,
  };

View on GitHub (pinned to 766dce6b13)