Hmbown/CodeWhale · error · Error
No spans found in resourceSpans[].scopeSpans[].spans[].
Error message
No spans found in resourceSpans[].scopeSpans[].spans[].
What it means
fromOTLP parses an OTLP JSON trace-export document and expects spans under resourceSpans[].scopeSpans[].spans[]. otlpRecords found zero spans, so there is nothing to import; the library throws instead of returning an empty trace.
Solutions
- Verify the file is an OTLP traces JSON export containing resourceSpans[].scopeSpans[].spans[].
- Re-export with the correct signal: ensure the collector/SDK is configured to export spans (traces), not metrics/logs.
- Check the file isn't empty or truncated; re-run the export with a healthy pipeline.
- Ensure you use OTLP JSON encoding, not protobuf bytes, for this JSON parser.
Example fix
// before (wrong signal export)
{ "resourceMetrics": [ ... ] }
// after
{ "resourceSpans": [ { "scopeSpans": [ { "spans": [ { "traceId": "...", "spanId": "...", "name": "op", "startTimeUnixNano": "1", "endTimeUnixNano": "2" } ] } ] } ] } Defensive patterns
Strategy: validation
Validate before calling
const doc = JSON.parse(text);
const spanCount = (doc.resourceSpans ?? []).flatMap((rs) => rs.scopeSpans ?? []).flatMap((ss) => ss.spans ?? []).length;
if (spanCount === 0) throw new Error('Not an OTLP traces JSON export (no spans found)'); Type guard
const isOtlpTraceDoc = (d: unknown): d is { resourceSpans: { scopeSpans: { spans: unknown[] } }[] } =>
typeof d === 'object' && d !== null && 'resourceSpans' in d &&
Array.isArray((d as any).resourceSpans); Try / catch
try {
traces = importTrace(text, file);
} catch (e) {
if (e instanceof Error && e.message.includes('No spans found')) {
console.error('File has no spans: check signal type (traces vs metrics/logs) and OTLP JSON format');
} else throw e;
} Prevention
- Confirm the exporter emits the traces signal (spans), not metrics or logs.
- Use OTLP/HTTP with JSON encoding when feeding a JSON parser.
- Check file size > 0 and that the export completed before importing.
When it happens
Trigger: Calling the OTLP import path with a document where resourceSpans is missing, empty, scopeSpans is empty, spans arrays are empty, or the JSON is valid but of a different shape (e.g. metrics or logs export, or protobuf-encoded bytes pasted as text).
Common situations: Exporting from an OTel collector with the wrong signal configured; passing an OTLP/HTTP protobuf body instead of the JSON format; truncation of the export; pointing the importer at a logs/metrics export file.
Understand the failure class
Background: EmptyResultError / "no results found": when an API or scraper succeeds but returns zero rows — this error's family across 9 libraries.
Related errors
- Every OTLP span requires traceId and spanId.
- Import exceeds the event limit, including span events.
- Import exceeds the event limit.
- OTLP span : end precedes start.
- The file contains no events.
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/2171e76d894fc60e.
Report an issue: GitHub.
Appendix: source
Thrown at pet/src/core/ingest.ts:164
sourceId: str(a.sourceId), targetId: str(a.targetId), targetType: str(a.targetType),
links: list(a.links).filter(x => typeof x?.traceId === 'string' && typeof x?.spanId === 'string'),
attributes: at, payload: a.payload, raw: a.raw ?? v,
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 = {View on GitHub (pinned to 433685b202)