Hmbown/CodeWhale · error · Error

Pet tape exceeds 24 hours.

Error message

Pet tape exceeds 24 hours.

What it means

decodePetJSONL() parses a persisted 'pet tape' (JSONL of pet buckets) and enforces hard limits: the tape may cover at most 216,000 one-second buckets, i.e. 24 hours of data. If the decoded tape holds more rows than that, the parser refuses it rather than loading an oversized tape.

Solutions

  1. Rotate or truncate the tape file so it holds at most 216,000 rows (24 hours of 1-second buckets).
  2. Split the tape into per-day files and decode each segment separately.
  3. Raise the 216_000 cap only if 1-second bucketing assumptions genuinely changed — otherwise keep the limit.
  4. Export the tape to a compacted/downsampled format before decoding.

Example fix

// before
decodePetJSONL(fs.readFileSync('pet-tape.jsonl', 'utf8')); // throws on >216k rows
// after
const rows = fs.readFileSync('pet-tape.jsonl', 'utf8').trim().split('\n');
const last24h = rows.slice(-216_000).join('\n');
decodePetJSONL(last24h);
Defensive patterns

Strategy: validation

Validate before calling

function canDecodeTape(text) {
  const rows = text.split(/\r?\n/).filter(l => l.trim());
  return rows.length <= 216_000;
}
if (!canDecodeTape(raw)) raw = raw.split('\n').slice(-216_000).join('\n');

Type guard

function isWithinTapeCapacity(rows) {
  return Array.isArray(rows) && rows.length <= 216_000;
}

Try / catch

try {
  const tape = decodePetJSONL(raw);
} catch (e) {
  if (e.message.includes('exceeds 24 hours')) {
    const tail = raw.split(/\r?\n/).filter(Boolean).slice(-216_000).join('\n');
    const tape = decodePetJSONL(tail);
  } else throw e;
}

Prevention

When it happens

Trigger: Calling decodePetJSONL (directly or via the pet-watch tape loader) on a JSONL file whose non-empty line count exceeds 216,000 — e.g. a tape was appended to for more than 24 hours, or an earlier run wrote without rotation.

Common situations: Long-running pet-watch sessions that never rotate the tape file; clock/timezone changes inflating bucket counts; a merged or concatenated tape from several sessions.

Understand the failure class

Background: "File too large" / "file size exceeds limit" errors: why libraries cap file sizes and how to fix them — this error's family across 46 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/bd62b7701409553c. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/tui/pet_watch/pet-native.js:1118

exports.PET_BIN_MS = 400;
function validatePetBucket(value) {
    (0, pet_sim_js_1.validatePetState)(value);
    const b = value;
    if (!b || typeof b !== 'object' || b.version !== 1 || !Number.isSafeInteger(b.sequence) || b.sequence < 0 || b.sequence > pet_sim_js_1.PET_MAX_SECONDS * 2.5
        || b.simTimeMs !== b.sequence * exports.PET_BIN_MS || b.durationMs !== exports.PET_BIN_MS
        || !model_js_1.CATEGORIES.includes(b.channel) || typeof b.waiting !== 'boolean'
        || !Array.isArray(b.agentIds) || b.agentIds.length > 250_000 || b.agentIds.some(id => typeof id !== 'string' || !id || id.length > 4096)
        || !Number.isSafeInteger(b.errors) || b.errors < 0 || b.errors > 250_000
        || !Array.isArray(b.onsets) || b.onsets.length !== 13 || b.onsets.some(n => !Number.isSafeInteger(n) || n < 0 || n > 250_000)
        || !Array.isArray(b.activeMs) || b.activeMs.length !== 13 || b.activeMs.some(n => !Number.isFinite(n) || n < 0 || n > 100_000_000))
        throw new Error('Invalid version 1 pet bucket.');
}
function decodePetJSONL(text) {
    if (text.length > 64 * 1024 * 1024)
        throw new Error('Pet tape exceeds 64 MiB.');
    const rows = text.split(/\r?\n/).filter(line => line.trim()).map(line => JSON.parse(line));
    if (rows.length > 216_000)
        throw new Error('Pet tape exceeds 24 hours.');
    return rows.map((row, i) => { validatePetBucket(row); if (row.sequence !== i)
        throw new Error('Non-contiguous pet tape.'); return row; });
}
/** A live file must advance before its contents count as a new observation.
 * Existing bytes, duplicate samples and a restarted sequence establish a
 * baseline; they never replay an old onset or human request. Drivers supply a
 * bounded tail and reset this cursor after suspension or a new attachment. */
class PetLiveTape {
    sequence;
    reset() { this.sequence = undefined; }
    readTail(text) {
        if (!text) {
            this.reset();
            return;
        }
        if (text.length > 262_144) {
            this.reset();
            throw new Error('Live pet input exceeds its tail limit.');

View on GitHub (pinned to 73e0f67d83)