Hmbown/CodeWhale · error · Error

Every OTLP span requires traceId and spanId.

Error message

Every OTLP span requires traceId and spanId.

What it means

Every OTLP span must carry non-empty traceId and spanId fields, since the importer keys spans, builds parent-child links, and derives per-trace origins from them. A span missing either identity is rejected strictly — malformed lines or duplicate identities never disappear silently.

Solutions

  1. Fix the exporter to always set traceId (16-byte hex) and spanId (8-byte hex) on every span.
  2. Repair the specific span record in the JSON before importing.
  3. Re-export from the original source instead of editing intermediate files, if truncation stripped fields.

Example fix

// before
{ "name": "op", "startTimeUnixNano": "1", "endTimeUnixNano": "2" }
// after
{ "traceId": "5b8efff798038103d269b633813fc60c", "spanId": "eee19b7ec3c1b174", "name": "op", "startTimeUnixNano": "1", "endTimeUnixNano": "2" }
Defensive patterns

Strategy: validation

Validate before calling

const hex = (s, len) => typeof s === 'string' && s.length === len && /^[0-9a-f]+$/.test(s);
for (const span of allSpans(doc)) {
  if (!hex(span.traceId, 32) || !hex(span.spanId, 16))
    throw new Error(`Span missing valid traceId/spanId: ${JSON.stringify(span).slice(0, 80)}`);
}

Type guard

const hasIds = (s: unknown): s is { traceId: string; spanId: string } =>
  typeof s === 'object' && s !== null &&
  typeof (s as any).traceId === 'string' && (s as any).traceId.length > 0 &&
  typeof (s as any).spanId === 'string' && (s as any).spanId.length > 0;

Try / catch

try {
  traces = importTrace(text, file);
} catch (e) {
  if (e instanceof Error && e.message.includes('requires traceId and spanId')) {
    console.error('A span lacks IDs — re-export or fix the exporter');
  } else throw e;
}

Prevention

When it happens

Trigger: Importing an OTLP JSON document where any span object lacks traceId or spanId, has them as empty strings, or where the fields failed hex/string validation in str().

Common situations: Hand-rolled exporters omitting IDs for synthetic spans; collector transformations that strip attributes; partial/truncated exports; fixtures written by hand without valid 16/8-byte hex IDs.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15). Data as JSON: /api/errors/200a2094ee9cfc88. Report an issue: GitHub.

Appendix: source

Thrown at pet/src/core/ingest.ts:167

    observation: a.observation === undefined ? undefined : validateObservation(a.observation),
  };
}
interface OtlpRecord { span: Obj; resource: Obj; scope: Obj; resourceSchema?: string; scopeSchema?: string }
function otlpRecords(doc: Obj): OtlpRecord[] {
  const out: OtlpRecord[] = [];
  for (const r of list(doc.resourceSpans)) {
    for (const s of list(r.scopeSpans ?? r.instrumentationLibrarySpans)) {
      for (const span of list(s.spans)) out.push({ span: obj(span), resource: obj(r.resource), scope: obj(s.scope ?? s.instrumentationLibrary), resourceSchema: r.schemaUrl, scopeSchema: s.schemaUrl });
    }
  }
  return out;
}
function fromOTLP(doc: Obj, maxEvents: number): { events: WhaleEvent[]; origins: Map<string, string>; warnings: string[] } {
  const records = otlpRecords(doc), bases = new Map<string, bigint>(), warnings: string[] = [];
  if (!records.length) throw new Error('No spans found in resourceSpans[].scopeSpans[].spans[].');
  if (records.length > maxEvents) throw new Error(`Import exceeds the ${maxEvents.toLocaleString()} event limit.`);
  for (const { span: s } of records) {
    if (!str(s.traceId) || !str(s.spanId)) throw new Error('Every OTLP span requires traceId and spanId.');
    const start = ns(s.startTimeUnixNano, 'startTimeUnixNano');
    const end = s.endTimeUnixNano === undefined ? start : ns(s.endTimeUnixNano, 'endTimeUnixNano');
    if (end < start) throw new Error(`OTLP span ${s.spanId}: end precedes start.`);
    let earliest = start;
    for (const e of list(s.events)) { const t = ns(e.timeUnixNano, 'event.timeUnixNano'); if (t < earliest) earliest = t; }
    if (!bases.has(s.traceId) || earliest < bases.get(s.traceId)!) bases.set(s.traceId, earliest);
    if ((s.droppedEventsCount ?? 0) > 0) warnings.push(`Span ${s.spanId} reports ${s.droppedEventsCount} dropped events; coverage is incomplete.`);
  }
  const events: WhaleEvent[] = [];
  for (const rec of records) {
    const s = rec.span, a = { ...attributes(rec.resource.attributes), ...attributes(s.attributes) };
    const origin = bases.get(s.traceId)!, start = ns(s.startTimeUnixNano, 'startTimeUnixNano');
    const end = s.endTimeUnixNano === undefined ? start : ns(s.endTimeUnixNano, 'endTimeUnixNano');
    const name = String(s.name ?? 'unnamed span');
    const e: WhaleEvent = {
      schemaVersion: 1, id: s.spanId, traceId: s.traceId, parentId: str(s.parentSpanId),
      name, startTime: Number(start - origin) / 1e6, endTime: Number(end - origin) / 1e6,
      openEnded: s.endTimeUnixNano === undefined,

View on GitHub (pinned to 433685b202)