{"record":{"id":"2c4cfe5a713e3d83","repo":"Hmbown/CodeWhale","slug":"duplicate-event-identity-e-traceid-e-id-import-cancelled","errorCode":null,"errorMessage":"Duplicate event identity (${e.traceId}, ${e.id}). Import cancelled.","messagePattern":"Duplicate event identity \\((.+?), (.+?)\\)\\. Import cancelled\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"pet/src/core/ingest.ts","lineNumber":263,"sourceCode":"  if (isCodewhaleRuntimeDocument(records ?? [safe])) {\n    const trace = fromCodewhaleRuntime(records ?? [safe], filename, maxEvents);\n    trace.privacy = mode;\n    trace.events = trace.events.map(e => privacyEvent(e, mode));\n    return [trace];\n  }\n  const isOTLP = Array.isArray(root.resourceSpans);\n  let all: WhaleEvent[], origins = new Map<string, string>(), warnings: string[] = [];\n  if (isOTLP) { const r = fromOTLP(root, maxEvents); all = r.events; origins = r.origins; warnings = r.warnings; }\n  else {\n    const incoming = records ?? [safe];\n    if (incoming.length > maxEvents) throw new Error(`Import exceeds the ${maxEvents.toLocaleString()} event limit.`);\n    all = incoming.map(normalizedEvent);\n  }\n  if (!all.length) throw new Error('The file contains no events.');\n  const groups = new Map<string, WhaleEvent[]>(), ids = new Set<string>();\n  for (const e of all) {\n    const key = `${e.traceId}\\0${e.id}`;\n    if (ids.has(key)) throw new Error(`Duplicate event identity (${e.traceId}, ${e.id}). Import cancelled.`);\n    ids.add(key);\n    const group = groups.get(e.traceId) ?? []; group.push(privacyEvent(e, mode)); groups.set(e.traceId, group);\n    if (groups.size > maxTraces) throw new Error(`This import contains more than ${maxTraces} traces. Split it by trace ID.`);\n  }\n  return [...groups].map(([id, events]) => {\n    events.sort((a, b) => a.startTime - b.startTime || a.id.localeCompare(b.id));\n    const base = isOTLP ? 0 : events[0].startTime;\n    if (!isOTLP && events.some(e => Math.abs(e.endTime - base) > Number.MAX_SAFE_INTEGER)) throw new Error('Trace duration exceeds safely representable milliseconds.');\n    for (const e of events) {\n      if (e.attributes['whalesong.error_onset_ms'] !== undefined) e.attributes['whalesong.error_onset_ms'] = errorOnsetOf(e) - base;\n      e.startTime -= base; e.endTime -= base;\n    }\n    const localIds = new Set(events.map(e => e.id)), missingParents = events.filter(e => e.parentId && !localIds.has(e.parentId)).length;\n    const traceWarnings = [...warnings];\n    if (missingParents) traceWarnings.push(`${missingParents} parent spans are absent from this trace; no parent relationship was invented.`);\n    if (events.some(e => e.openEnded)) traceWarnings.push('Open spans have unknown duration and are displayed as onset-only, not extended into invented activity.');\n    const currencies = new Set(events.filter(e => e.cost !== undefined).map(e => e.costCurrency ?? 'unspecified'));\n    if (currencies.size > 1) traceWarnings.push('Mixed cost currencies: aggregate cost comparison is disabled.');","sourceCodeStart":245,"sourceCodeEnd":281,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/pet/src/core/ingest.ts#L245-L281","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nimportTrace(text); // lines contain duplicated events\n// after\nconst seen = new Set();\nconst lines = text.split('\\n').filter(l => {\n  const e = JSON.parse(l); const k = `${e.traceId}\\u0000${e.id}`;\n  if (seen.has(k)) return false; seen.add(k); return true;\n});\nimportTrace(lines.join('\\n'));","handlingStrategy":"validation","validationCode":"const seen = new Set<string>();\nfor (const e of events) {\n  const k = `${e.traceId}\\u0000${e.id}`;\n  if (seen.has(k)) throw new Error(`Duplicate event ${k} before import`);\n  seen.add(k);\n}","typeGuard":null,"tryCatchPattern":"try {\n  importTrace(text);\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('Duplicate event identity')) {\n    console.error('Deduplicate by (traceId, id) and retry.');\n  }\n}","preventionTips":["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."],"tags":["deduplication","validation","ingest"],"backgroundTag":"conflicting-config-options","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-22T11:17:16.035Z"}