Hmbown/CodeWhale · error · Error
Non-contiguous pet tape.
Error message
Non-contiguous pet tape.
What it means
decodePetJSONL() requires each row's 'sequence' field to equal its 0-based index in the file, proving the tape is contiguous. A gap or restart in sequence numbers means rows were dropped, reordered, or a new tape was appended to an old one, so the parser rejects the whole file.
Solutions
- Regenerate or repair the tape so sequence numbers run 0..n-1 with no gaps.
- Reindex the rows before decoding: rows.map((row,i)=>({...row,sequence:i})).
- Do not concatenate tapes; keep one file per session and decode them separately.
- Delete the corrupted tape and let the pet-watch driver start a fresh one.
Example fix
// before
const tape = decodePetJSONL(raw); // throws 'Non-contiguous pet tape.'
// after
const rows = raw.split(/\r?\n/).filter(Boolean).map(JSON.parse).map((r, i) => ({ ...r, sequence: i }));
const tape = decodePetJSONL(rows.map(r => JSON.stringify(r)).join('\n')); Defensive patterns
Strategy: validation
Validate before calling
function isContiguous(raw) {
const rows = raw.split(/\r?\n/).filter(l => l.trim()).map(JSON.parse);
return rows.every((r, i) => r.sequence === i);
} Type guard
function hasContiguousSequence(rows) {
return Array.isArray(rows) && rows.every((r, i) =>
r != null && typeof r.sequence === 'number' && r.sequence === i);
} Try / catch
try {
const tape = decodePetJSONL(raw);
} catch (e) {
if (e.message === 'Non-contiguous pet tape.') {
// reindex and retry, or start a fresh tape
const repaired = raw.split(/\r?\n/).filter(Boolean)
.map((l, i) => JSON.stringify({ ...JSON.parse(l), sequence: i })).join('\n');
const tape = decodePetJSONL(repaired);
} else throw e;
} Prevention
- Never hand-edit or splice tape files; regenerate instead.
- Keep one tape per session; do not concatenate.
- After a writer crash, rebuild the tape rather than patching lines.
- Validate sequence continuity at write time, not read time.
When it happens
Trigger: Decoding a JSONL tape where any row's sequence !== its row index — e.g. lines were deleted, rows were written out of order, multiple sessions' tapes were concatenated, or the sequence counter restarted at 0 mid-file.
Common situations: Manually editing/trimming the tape file; merging tapes from two runs; a crashed writer that skipped a line; appending a new session's tape (sequence restarted at 0) to an existing file.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- Pet tape exceeds 24 hours.
- The previous pet recording has an incomplete final row; it…
- artifact id and extension must contain safe ASCII characters
- ; additionally could not verify secret-store rollback for
- external credential path contains a NUL byte
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/8abdf23edbf734dc.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/tui/pet_watch/pet-native.js:1120
(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.');
}
if (!text.endsWith('\n'))View on GitHub (pinned to 73e0f67d83)