{"record":{"id":"c7f8a63e7dcff004","repo":"Hmbown/CodeWhale","slug":"invalid-pet-duration-telemetry","errorCode":null,"errorMessage":"Invalid pet duration.","messagePattern":"Invalid pet duration\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"pet/src/core/pet-telemetry.ts","lineNumber":72,"sourceCode":"    try { packet = JSON.parse(line); validatePetBucket(packet); }\n    catch (error) { this.reset(); throw error; }\n    const previous = this.sequence; this.sequence = packet.sequence;\n    if (previous === undefined || packet.sequence <= previous) return;\n    return packet;\n  }\n}\n\nconst order = (a: string, b: string) => a < b ? -1 : a > b ? 1 : 0;\nconst keyOf = (e: WhaleEvent) => JSON.stringify([e.traceId, e.id]);\nconst isContainer = (e: WhaleEvent) => e.attributes['whalesong.container'] === true\n  || e.attributes['codewhale.container'] === true;\n\n/** Compile a single trace. Unknown-duration spans provide onsets, not occupancy.\n * Updates of the same trace/id replace earlier snapshots rather than double count.\n * An endpoint onset gets its own bucket; intervals use [start, end). */\nexport function compilePetTelemetry(input: readonly WhaleEvent[], durationMs = 0, firstSequence = 0, originMs = 0): PetBucket[] {\n  if (input.length > 250_000) throw new Error('Pet input exceeds 250000 events.');\n  if (!Number.isFinite(durationMs) || durationMs < 0) throw new Error('Invalid pet duration.');\n  if (!Number.isSafeInteger(firstSequence) || firstSequence < 0) throw new Error('Invalid first pet bucket.');\n  if (!Number.isFinite(originMs)) throw new Error('Invalid pet clock origin.');\n  durationMs = Math.max(0, durationMs - originMs);\n  const unique = new Map<string, WhaleEvent>();\n  const traces = new Set<string>();\n  for (const e of input) {\n    if (e.schemaVersion !== 1 || !e.id || !e.traceId || !CATEGORIES.includes(e.category)\n      || !Number.isFinite(e.startTime) || !Number.isFinite(e.endTime)\n      || e.startTime < 0 || e.endTime < e.startTime || !e.attributes)\n      throw new Error('Invalid event-v1 pet input. Import through importTrace first.');\n    traces.add(e.traceId); unique.set(keyOf(e), e);\n  }\n  if (traces.size > 1) throw new Error('Select one trace for the pet.');\n  const parents = new Set([...unique.values()].filter(e => e.parentId).map(e => e.parentId!));\n  const events = [...unique.values()].filter(e => !isContainer(e)\n    && !(e.category === 'orchestration' && parents.has(e.id)))\n    .map(e => ({ ...e, startTime: e.startTime - originMs, endTime: (e.openEnded ? e.startTime : e.endTime) - originMs,\n      attributes: e.attributes['whalesong.error_onset_ms'] === undefined ? e.attributes","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/pet/src/core/pet-telemetry.ts#L54-L90","documentation":"Guard at the top of compilePetTelemetry: fires when the durationMs argument is not a finite number or is negative (NaN, Infinity, or < 0). The caller passed an invalid trace duration; only a finite non-negative duration can be divided into pet buckets.","triggerScenarios":"Passing a durationMs that is NaN (e.g. from a failed Date arithmetic or undefined subtraction), negative (end before start), or Infinity (missing end timestamp).","commonSituations":"Computing duration as Date.now() - undefinedStart; a span whose end timestamp was absent so subtraction yielded NaN; timezone/clock adjustments producing negative windows; JSON round-trips turning a number into null then NaN via coercion.","solutions":["Clamp the duration: Math.max(0, endMs - startMs) and verify Number.isFinite before calling","Default missing end timestamps to start (zero duration) or to now","Validate timestamps at the span-producing layer so bad spans never reach compile","Skip spans with non-finite timestamps and log them"],"exampleFix":"// before\ncompilePetTelemetry(events, endMs - startMs); // NaN if startMs undefined\n// after\nconst duration = Number.isFinite(endMs - startMs) ? Math.max(0, endMs - startMs) : 0;\ncompilePetTelemetry(events, duration);","handlingStrategy":"validation","validationCode":"const duration = endMs != null && Number.isFinite(endMs - startMs) ? Math.max(0, endMs - startMs) : 0;\nif (!Number.isFinite(duration) || duration < 0) throw new Error('bad duration before compile');","typeGuard":"function isValidDuration(ms: unknown): ms is number {\n  return typeof ms === 'number' && Number.isFinite(ms) && ms >= 0;\n}","tryCatchPattern":"try {\n  compilePetTelemetry(events, duration);\n} catch (e) {\n  if (e.message === 'Invalid pet duration.') {\n    compilePetTelemetry(events, 0); // treat as zero-duration window\n  } else throw e;\n}","preventionTips":["Default missing span ends to the start time or to now","Guard all timestamp subtraction with Number.isFinite","Clamp with Math.max(0, ...) before passing durations"],"tags":["validation","duration","telemetry"],"backgroundTag":"invalid-argument-value","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-22T21:17:16.096Z"}