Hmbown/CodeWhale · error · Error
Duplicate event identity
Error message
Duplicate event identity (${e.traceId}, ${e.id}). Import cancelled. What it means
Event identity is the pair (traceId, event id). importTrace strictly rejects duplicates — it never dedupes silently — so re-importing a file or a file containing the same span twice aborts the whole import with this message naming the offending pair.
Solutions
- Deduplicate by `${traceId}\0${id}` before calling importTrace (keep the first or latest occurrence).
- Use non-overlapping export windows so each event appears once.
- If re-importing intentionally, drop existing traces/events with the same IDs first.
Example fix
// before
importTrace(text); // lines contain duplicated events
// after
const seen = new Set();
const lines = text.split('\n').filter(l => {
const e = JSON.parse(l); const k = `${e.traceId}\u0000${e.id}`;
if (seen.has(k)) return false; seen.add(k); return true;
});
importTrace(lines.join('\n')); Defensive patterns
Strategy: validation
Validate before calling
const seen = new Set<string>();
for (const e of events) {
const k = `${e.traceId}\u0000${e.id}`;
if (seen.has(k)) throw new Error(`Duplicate event ${k} before import`);
seen.add(k);
} Try / catch
try {
importTrace(text);
} catch (e) {
if (e instanceof Error && e.message.startsWith('Duplicate event identity')) {
console.error('Deduplicate by (traceId, id) and retry.');
}
} Prevention
- Deduplicate by (traceId, id) before importing concatenated files.
- Use disjoint export time windows so events appear exactly once.
- Don't re-append retry exports to the same JSONL file.
- If re-importing, drop previously imported events with the same IDs first.
When it happens
Trigger: importTrace on a JSONL file that contains the same event twice, concatenating overlapping exports (shared events appear in both), or retrying an import after a partial append duplicated lines.
Common situations: Merging two overlapping time-window exports; a retry that re-appended to the same file; exporter retry logic writing the same span again; copy-paste duplication when hand-editing JSONL.
Understand the failure class
Background: Conflicting config options: "cannot be used together" — configuration validation errors across open-source libraries — this error's family across 162 libraries.
Related errors
- must be nonnegative.
- File exceeds the 64 MiB MVP import limit. Split the export…
- Invalid : expected a finite, safely representable number.
- maxTraces must be in [1, 64].
- Missing . Times must be numeric milliseconds.
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/2c4cfe5a713e3d83.
Report an issue: GitHub.
Appendix: source
Thrown at pet/src/core/ingest.ts:263
if (isCodewhaleRuntimeDocument(records ?? [safe])) {
const trace = fromCodewhaleRuntime(records ?? [safe], filename, maxEvents);
trace.privacy = mode;
trace.events = trace.events.map(e => privacyEvent(e, mode));
return [trace];
}
const isOTLP = Array.isArray(root.resourceSpans);
let all: WhaleEvent[], origins = new Map<string, string>(), warnings: string[] = [];
if (isOTLP) { const r = fromOTLP(root, maxEvents); all = r.events; origins = r.origins; warnings = r.warnings; }
else {
const incoming = records ?? [safe];
if (incoming.length > maxEvents) throw new Error(`Import exceeds the ${maxEvents.toLocaleString()} event limit.`);
all = incoming.map(normalizedEvent);
}
if (!all.length) throw new Error('The file contains no events.');
const groups = new Map<string, WhaleEvent[]>(), ids = new Set<string>();
for (const e of all) {
const key = `${e.traceId}\0${e.id}`;
if (ids.has(key)) throw new Error(`Duplicate event identity (${e.traceId}, ${e.id}). Import cancelled.`);
ids.add(key);
const group = groups.get(e.traceId) ?? []; group.push(privacyEvent(e, mode)); groups.set(e.traceId, group);
if (groups.size > maxTraces) throw new Error(`This import contains more than ${maxTraces} traces. Split it by trace ID.`);
}
return [...groups].map(([id, events]) => {
events.sort((a, b) => a.startTime - b.startTime || a.id.localeCompare(b.id));
const base = isOTLP ? 0 : events[0].startTime;
if (!isOTLP && events.some(e => Math.abs(e.endTime - base) > Number.MAX_SAFE_INTEGER)) throw new Error('Trace duration exceeds safely representable milliseconds.');
for (const e of events) {
if (e.attributes['whalesong.error_onset_ms'] !== undefined) e.attributes['whalesong.error_onset_ms'] = errorOnsetOf(e) - base;
e.startTime -= base; e.endTime -= base;
}
const localIds = new Set(events.map(e => e.id)), missingParents = events.filter(e => e.parentId && !localIds.has(e.parentId)).length;
const traceWarnings = [...warnings];
if (missingParents) traceWarnings.push(`${missingParents} parent spans are absent from this trace; no parent relationship was invented.`);
if (events.some(e => e.openEnded)) traceWarnings.push('Open spans have unknown duration and are displayed as onset-only, not extended into invented activity.');
const currencies = new Set(events.filter(e => e.cost !== undefined).map(e => e.costCurrency ?? 'unspecified'));
if (currencies.size > 1) traceWarnings.push('Mixed cost currencies: aggregate cost comparison is disabled.');View on GitHub (pinned to 433685b202)