Hmbown/CodeWhale · error · Error

maxBins must be an integer in [16, 1048576].

Error message

maxBins must be an integer in [16, 1048576].

What it means

buildPyramid() allocates a multi-level signal pyramid from whale events and lets callers tune the finest bin resolution via maxBins. It throws when maxBins is not an integer or falls outside [16, 1048576], because bin sizes are derived from it (duration / (maxBins - 1)) and absurd or fractional values would corrupt the pyramid layout.

Solutions

  1. Pass an integer between 16 and 1,048,576 (default 16,384 is fine for most traces).
  2. If the value comes from user input, parse with Number.parseInt and clamp: maxBins = Math.min(1_048_576, Math.max(16, Math.round(value))).
  3. If you meant to control bin width in ms, compute the count instead: maxBins = Math.ceil(duration / binWidthMs), then clamp.
  4. Check for string/number confusion when the value originates from JSON, CLI args, or URL params.

Example fix

// before
buildPyramid(events, duration, '16384');
// after
const maxBins = Math.min(1_048_576, Math.max(16, Math.round(Number('16384'))));
buildPyramid(events, duration, maxBins);
Defensive patterns

Strategy: validation

Validate before calling

const maxBins = Math.min(1_048_576, Math.max(16, Math.round(raw)));
if (!Number.isInteger(raw) && Number.isFinite(raw)) /* decided above: round+clamp is safe */;

Type guard

function isValidMaxBins(v: unknown): v is number { return Number.isInteger(v) && (v as number) >= 16 && (v as number) <= 1_048_576; }

Try / catch

try { buildPyramid(events, duration, maxBins); } catch (e) { if (/maxBins must be/.test(e.message)) buildPyramid(events, duration, 16_384); else throw e; }

Prevention

When it happens

Trigger: Calling buildPyramid(events, duration, maxBins) with a non-integer (e.g. 100.5), a value below 16 (including the naive 0 or 1 passed to mean 'no binning'), or above 1,048,576 (e.g. passing Number.MAX_SAFE_INTEGER to force max resolution).

Common situations: A config knob read from user input or a query string arriving as a string like '16384' that fails Number.isInteger after coercion quirks; a caller passing a desired bin *width* (e.g. 100 for 100ms) instead of a bin *count*; unbounded user zoom levels exceeding the cap.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


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

Appendix: source

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

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;

View on GitHub (pinned to 433685b202)