Hmbown/CodeWhale · error · Error
Invalid pet export cursor.
Error message
Invalid pet export cursor.
What it means
recordingChunk(index) exports a recording in fixed-size chunks addressed by a cursor. The index must be a safe non-negative integer; drivers consume chunks synchronously starting at 0. A negative, fractional, or out-of-IEEE-range index cannot address a chunk, so the export is aborted with this error.
Solutions
- Start the cursor at 0 and increment by 1 per chunk: `for (let i = 0; ; i++) { const chunk = w.recordingChunk(i); if (!chunk) break; }`.
- Guard the index with Number.isSafeInteger(index) && index >= 0 before calling.
- Round or floor any computed index before passing it in.
- Fix the loop termination so the cursor never goes negative after the final chunk.
Example fix
// before
let cursor = totalChunks; // or a computed fraction
world.recordingChunk(cursor--);
// after
for (let i = 0; i < totalChunks; i++) {
const chunk = world.recordingChunk(i);
// consume chunk synchronously
} Defensive patterns
Strategy: validation
Validate before calling
if (!Number.isSafeInteger(index) || index < 0)
throw new Error('recordingChunk index must be a safe non-negative integer'); Type guard
const isValidChunkIndex = (i) => Number.isSafeInteger(i) && i >= 0;
Try / catch
try {
const chunk = world.recordingChunk(i);
} catch (err) {
if (err.message === 'Invalid pet export cursor.')
console.error('Bad export cursor', i);
else throw err;
} Prevention
- Drive exports with a simple `for (let i = 0; ; i++)` loop that stops on the final chunk.
- Floor any computed index; never pass fractions or NaN.
- Clamp the cursor to >= 0 when looping backwards or retrying.
- Consume chunks synchronously on the world's owner, as the API requires.
When it happens
Trigger: Calling `world.recordingChunk(index)` with a non-integer (e.g. 1.5), a negative number, a value beyond Number.MAX_SAFE_INTEGER, or NaN/Infinity.
Common situations: An off-by-one or decrement-below-zero loop over chunk indices; dividing to compute an index and getting a fraction; a driver passing an uninitialized/NaN cursor; continuing a chunk loop after completion returned and the cursor arithmetic went negative.
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
- Invalid pet export cursor.
- Invalid pet export cursor.
- A provider and page loader are required.
- artifact tree exceeds export depth limit
- artifact tree exceeds export entry limit
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/9e3980483b66583f.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/tui/pet_watch/pet-native.js:305
if (committed || this.origin !== previous || this.tick < c.tick)
throw new Error('The pet recording segment has changed.');
this.tapeLog.splice(0, dropTape);
this.interactionLog.splice(0, dropInputs);
this.bucketIndex -= dropTape;
this.interactionIndex -= dropInputs;
this.tapeHashes = [];
this.hashTape(0);
this.segmented = true;
this.origin = next.origin;
committed = true;
} };
}
/** Lossless version 1 export, including the current pose and score. Drivers
* consume all chunks synchronously on the world's owner before another tick.
* Only a small slice is serialized inside an embedded runtime at a time. */
recordingChunk(index, completed = false) {
if (!Number.isSafeInteger(index) || index < 0)
throw new Error('Invalid pet export cursor.');
const size = 16, tapeEnd = completed ? this.bucketIndex + 1 : this.tapeLog.length;
const inputEnd = completed ? this.interactionIndex : this.interactionLog.length;
const tapes = Math.ceil(tapeEnd / size), inputs = Math.ceil(inputEnd / size);
if (index === 0)
return `{"petReplayVersion":${this.segmented ? 2 : 1},"expressionVersion":${this.sim.expressionVersion},"tape":[`;
if (index <= tapes)
return (index === 1 ? '' : ',') + JSON.stringify(this.tapeLog.slice((index - 1) * size, Math.min(tapeEnd, index * size))).slice(1, -1);
if (index === tapes + 1)
return '],"interactions":[';
const part = index - tapes - 2;
if (part < inputs)
return (part === 0 ? '' : ',') + JSON.stringify(this.interactionLog.slice(part * size, Math.min(inputEnd, (part + 1) * size))).slice(1, -1);
if (part === inputs) {
const history = this.historyDigest(tapeEnd, inputEnd);
return `]${this.origin ? ',"start":' + JSON.stringify({ ...this.origin, hasTelemetry: this.hasTelemetry, history }) : ''},"checkpoint":${JSON.stringify({ ...this.checkpoint(), history })}}`;
}
return null;
}View on GitHub (pinned to 73e0f67d83)