Hmbown/CodeWhale · error
Invalid pet recording segment limit.
Error message
Invalid pet recording segment limit.
What it means
createPetRecorder validates the caller-supplied segment limits before touching the filesystem: maxBuckets must be a safe integer in [1, 216000] and maxBytes must be a safe integer in [1, 64 MiB]. Any value outside those ranges (or a non-integer like NaN, Infinity, a float, or a string) throws this error immediately, because an unbounded or invalid limit would recreate the unbounded append-only output the recorder is designed to replace.
Solutions
- Pass only safe integers: maxBuckets between 1 and 216000 and maxBytes between 1 and 67108864, or omit both to use the defaults (216000 buckets, 64 MiB).
- If limits come from env/config, coerce with Number() and validate with Number.isSafeInteger plus range checks before calling createPetRecorder.
- Log the actual computed values just before the call to spot float or string leakage; round or floor derived byte values.
Example fix
// before
await createPetRecorder(path, { maxBytes: opts.maxBytes ?? '64MB' });
// after
const maxBytes = Number(opts.maxBytes ?? 64 * 1024 * 1024);
if (!Number.isSafeInteger(maxBytes) || maxBytes < 1 || maxBytes > 64 * 1024 * 1024) throw new Error(`bad maxBytes: ${opts.maxBytes}`);
await createPetRecorder(path, { maxBytes }); Defensive patterns
Strategy: validation
Validate before calling
function validLimits({ maxBuckets = 216000, maxBytes = 64 * 1024 * 1024 } = {}) {
return Number.isSafeInteger(maxBuckets) && maxBuckets >= 1 && maxBuckets <= 216000
&& Number.isSafeInteger(maxBytes) && maxBytes >= 1 && maxBytes <= 64 * 1024 * 1024;
} Type guard
const isSafeIntInRange = (v, lo, hi) => Number.isSafeInteger(v) && v >= lo && v <= hi;
Try / catch
try { await createPetRecorder(p, opts); } catch (e) { if (e.message === 'Invalid pet recording segment limit.') { console.error('bad limits', opts); process.exitCode = 2; } else throw e; } Prevention
- Never pass raw env/CLI strings as limits; coerce with Number() and validate first.
- Keep derived values integral: use Math.floor on computed byte counts.
- Prefer omitting the options to rely on library defaults unless a custom bound is genuinely needed.
When it happens
Trigger: Calling createPetRecorder(path, { maxBuckets: 0 }), a negative maxBytes, maxBuckets: 216001 (above cap), maxBytes: 64*1024*1024 + 1 (above 64 MiB cap), or non-safe-integers such as NaN, Infinity, 1000.5, or a numeric string parsed from CLI args/env without coercion.
Common situations: Config values read from environment variables or JSON left as strings instead of numbers; a caller passing 0 or undefined-typed placeholders to 'disable' rotation; copy-pasting the 64*1024*1024 default with a `+1` headroom tweak; computing limits with float arithmetic (e.g. bytes/1024/1024).
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
- --attempts must be an integer from 1 to 20
- --channel, --version and --reason are required
- Choose one Runtime --thread ID.
- Command is required
- Provide either --command or --url for `mcp add`.
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/4e89a54e190a0f54.
Report an issue: GitHub.
Appendix: source
Thrown at pet/scripts/lib/pet-recorder.mjs:53
};
try {
database = new DatabaseSync(name);
database.exec('PRAGMA busy_timeout = 0; BEGIN EXCLUSIVE');
await check();
return { check, close: () => { database.close(); } };
} catch (error) {
database?.close();
if (error.errcode === 5 || error.errcode === 6) throw new Error('Another pet recorder is using this output.');
throw error;
}
}
/** Replaces the CLI's unbounded append-only output. Each complete segment is
* replayable on its own; the same live pathname always holds the newest one. */
export async function createPetRecorder(path, { maxBuckets = 216_000, maxBytes = 64 * 1024 * 1024, report = () => {}, resume = false } = {}) {
if (!Number.isSafeInteger(maxBuckets) || maxBuckets < 1 || maxBuckets > 216_000
|| !Number.isSafeInteger(maxBytes) || maxBytes < 1 || maxBytes > 64 * 1024 * 1024)
throw new Error('Invalid pet recording segment limit.');
path = resolve(await realpath(dirname(resolve(path))), basename(path));
let lock = await lockRecorder(path), output, sequence = 0, bytes = 0, segment = 0, busy = false, restart = false, expectedMtime;
try {
try { output = await open(path, 'wx', 0o600); }
catch (error) {
if (!resume || error.code !== 'EEXIST') throw error;
const original = await lstat(path, { bigint: true });
if (!original.isFile() || original.size > 64n * 1024n * 1024n)
throw new Error('The previous pet recording is not a bounded regular file; it was preserved.');
output = await open(path, constants.O_RDWR | constants.O_APPEND | constants.O_NOFOLLOW | constants.O_NONBLOCK);
const held = await output.stat({ bigint: true });
if (held.dev !== original.dev || held.ino !== original.ino || held.size !== original.size)
throw new Error('The previous pet recording changed while opening; it was preserved.');
// Read at most the size already checked, including a single growth byte.
const contents = Buffer.alloc(Number(held.size) + 1);
let length = 0;
while (length < contents.length) {
const { bytesRead } = await output.read(contents, length, contents.length - length, length);View on GitHub (pinned to 433685b202)