Hmbown/CodeWhale · error · Error

Invalid interval for

Error message

Invalid interval for ${e.id}.

What it means

While binning events into the pyramid, buildPyramid() validates each WhaleEvent's interval. It throws this per-event error when startTime or endTime is not finite, startTime is negative, or endTime is earlier than startTime — an interval that cannot be placed on the time axis.

Solutions

  1. Fix the offending event's timestamps so 0 <= startTime <= endTime and both are finite; the error message names the event id to locate it.
  2. Validate/sanitize events before calling buildPyramid, dropping or repairing records with bad intervals.
  3. For in-flight spans, use a sentinel end equal to startTime or the current time, not a negative or undefined value.
  4. Check whether the import layer (OTLP/JSONL adapter) is leaving timestamps undefined.

Example fix

// before
buildPyramid([{ id: 'a', startTime: 100, endTime: 50, category: 'tool' }]);
// after
buildPyramid([{ id: 'a', startTime: 50, endTime: 100, category: 'tool' }]);
Defensive patterns

Strategy: validation

Validate before calling

const bad = events.filter(e => !Number.isFinite(e.startTime) || !Number.isFinite(e.endTime) || e.startTime < 0 || e.endTime < e.startTime);
if (bad.length) repairOrDrop(bad); // before buildPyramid

Type guard

function hasValidInterval(e: WhaleEvent): boolean { return Number.isFinite(e.startTime) && Number.isFinite(e.endTime) && e.startTime >= 0 && e.endTime >= e.startTime; }

Try / catch

try { buildPyramid(events, duration); } catch (e) { const m = /Invalid interval for (.+)\./.exec(e.message); if (m) { dropEvent(m[1]); buildPyramid(events, duration); } else throw e; }

Prevention

When it happens

Trigger: An event in the array has NaN/Infinity startTime or endTime (missing timestamps, null coerced via arithmetic), a negative startTime, or endTime < startTime (swapped/reversed timestamps, e.g. end recorded before start due to clock skew or a logic bug).

Common situations: Traces imported from external formats (OTLP, JSONL) with missing end times; events still in flight recorded with a placeholder end of -1 or undefined; swapping startTime/endTime arguments at the call site; clock skew between distributed producers.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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

Appendix: source

Thrown at pet/src/core/signal.ts:21

function emptyLevel(length: number, binMs: number): BinLevel {
  const n = CATEGORIES.length * length;
  return { binMs, length, onsets: new Float64Array(n), activeMs: new Float64Array(n),
    outputTokens: new Float64Array(n), cost: new Float64Array(n), errors: new Float64Array(n), peak: new Float64Array(n) };
}
const FIELDS = ['onsets', 'activeMs', 'outputTokens', 'cost', 'errors'] as const;
/** O(events + channels × bins), including long intervals. No span-length inner loop. */
export function buildPyramid(events: readonly WhaleEvent[], requestedDuration?: number, maxBins = 16_384): SignalPyramid {
  if (!Number.isInteger(maxBins) || maxBins < 16 || maxBins > 1_048_576) throw new Error('maxBins must be an integer in [16, 1048576].');
  let duration = requestedDuration ?? 1;
  for (const e of events) duration = Math.max(duration, e.endTime, e.startTime, e.status === 'error' ? errorOnsetOf(e) : 0);
  if (!Number.isFinite(duration) || duration < 0) throw new Error('Signal duration must be finite and nonnegative.');
  duration = Math.max(1, duration);
  const binMs = 2 ** Math.ceil(Math.log2(Math.max(1, duration / (maxBins - 1))));
  const length = Math.floor(duration / binMs) + 1, fine = emptyLevel(length, binMs);
  const stride = length + 1;
  const activeDiff = new Float64Array(CATEGORIES.length * stride), tokenDiff = new Float64Array(CATEGORIES.length * stride);
  for (const e of events) {
    if (!Number.isFinite(e.startTime) || !Number.isFinite(e.endTime) || e.startTime < 0 || e.endTime < e.startTime) throw new Error(`Invalid interval for ${e.id}.`);
    const channel = CATEGORIES.indexOf(e.category);
    if (channel < 0) throw new Error(`Unknown category for ${e.id}.`);
    const a = Math.floor(e.startTime / binMs), b = Math.floor(e.endTime / binMs);
    const at = channel * length, diff = channel * stride;
    fine.onsets[at + a]++;
    fine.cost[at + a] += e.cost ?? 0;
    if (e.status === 'error') fine.errors[at + Math.floor(errorOnsetOf(e) / binMs)]++;
    const d = e.endTime - e.startTime, tokens = e.outputTokens ?? 0;
    if (d === 0) { fine.outputTokens[at + a] += tokens; continue; }
    if (a === b) { fine.activeMs[at + a] += d; fine.outputTokens[at + a] += tokens; }
    else {
      const left = (a + 1) * binMs - e.startTime, right = e.endTime - b * binMs, tokenRate = tokens / d;
      fine.activeMs[at + a] += left;
      fine.activeMs[at + b] += right;
      fine.outputTokens[at + a] += left * tokenRate;
      fine.outputTokens[at + b] += right * tokenRate;
      if (b > a + 1) {
        activeDiff[diff + a + 1] += binMs; activeDiff[diff + b] -= binMs;

View on GitHub (pinned to 433685b202)