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
- Pass an integer between 16 and 1,048,576 (default 16,384 is fine for most traces).
- 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))).
- If you meant to control bin width in ms, compute the count instead: maxBins = Math.ceil(duration / binWidthMs), then clamp.
- 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
- Parse numeric config through one clamp/round helper before it reaches the API.
- Do not confuse bin width with bin count.
- Bound user-driven zoom inputs to the 16..1048576 range at the UI layer.
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
- A pinned task provider requires an explicit model
- agent profile reasoning_effort must be one of: inherit…
- Codewhale credentials path must be a directory
- Custom sub-agent requires a non-empty allowed_tools list
- Failed to update setting: invalid cost currency
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)