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
- Pass a finite, nonnegative requestedDuration explicitly (e.g. the trace's known total length in ms).
- Validate computed durations before calling: if (!Number.isFinite(d) || d < 0) reject or fall back to a default.
- Sanitize event timestamps on ingest so endTime/startTime are always finite numbers.
- 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
- Never pass undefined-derived arithmetic (null timestamps) as duration.
- Coerce imported durations with Number() and check isFinite.
- For empty traces pass 0, not NaN or Infinity.
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
- Invalid Engine pet clock.
- Invalid interval for
- Invalid Runtime retention horizon.
- maxBins must be an integer in [16, 1048576].
- World dt must be in [0, 10] seconds.
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)