Hmbown/CodeWhale · error · Error

Signal duration must be finite and nonnegative.

Error message

Signal duration must be finite and nonnegative.

What it means

buildPyramid() derives the signal duration either from the requestedDuration parameter or by scanning events for the max endTime/startTime/error onset. It throws when the resulting duration is NaN, Infinity, or negative, because bin size and pyramid level lengths cannot be computed from such a value.

Solutions

  1. Pass a finite, nonnegative requestedDuration explicitly (e.g. the trace's known total length in ms).
  2. Validate computed durations before calling: if (!Number.isFinite(d) || d < 0) reject or fall back to a default.
  3. Sanitize event timestamps on ingest so endTime/startTime are always finite numbers.
  4. Note the API clamps duration to at least 1ms internally, so pass 0 for empty traces rather than undefined-derived NaN.

Example fix

// before
buildPyramid(events, trace.end - trace.start); // NaN when trace.end is null
// after
const d = trace.end != null ? trace.end - trace.start : 0;
buildPyramid(events, Number.isFinite(d) && d >= 0 ? d : 0);
Defensive patterns

Strategy: validation

Validate before calling

const d = requested ?? 0;
if (!Number.isFinite(d) || d < 0) throw new TypeError('duration must be finite and >= 0');
buildPyramid(events, d);

Type guard

function isNonFiniteSafeDuration(v: unknown): v is number { return typeof v === 'number' && Number.isFinite(v) && v >= 0; }

Try / catch

try { buildPyramid(events, requestedDuration); } catch (e) { if (/duration must be finite/.test(e.message)) buildPyramid(events, 0); else throw e; }

Prevention

When it happens

Trigger: Passing requestedDuration = NaN/Infinity/negative (e.g. an unparsed string coerced to NaN, or a duration computed as endTime - startTime with a null endTime); or events carrying non-finite endTime/startTime values that poison the Math.max scan.

Common situations: Duration computed from missing/undefined timestamps; a JSON import where duration was a string like "1200"; clock skew producing a negative span; passing Infinity as 'unbounded' which the API does not accept.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

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

import { CATEGORIES, clamp, errorOnsetOf, quantile, type BinLevel, type Metric, type SignalPyramid, type WhaleEvent } from './model.js';

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 {

View on GitHub (pinned to 433685b202)