Hmbown/CodeWhale · error · Error
Invalid pet export cursor.
Error message
Invalid pet export cursor.
What it means
recordingChunk(index) paginates the lossless recording export into fixed-size chunks indexed from 0. A negative or non-safe-integer cursor cannot address any chunk, so the library rejects it up front rather than returning misleading data.
Solutions
- Pass a non-negative safe integer as the cursor, starting at 0 and incrementing by 1 until recordingChunk returns null.
- Coerce/validate the index with Number.isSafeInteger(i) && i >= 0 before calling.
- Stop iterating when recordingChunk returns null instead of guessing the last index.
Example fix
// before
let i = chunks.length - 1; // could be -1 on empty
world.recordingChunk(i); // throws when i === -1
// after
for (let i = 0; ; i++) { const c = world.recordingChunk(i); if (c === null) break; chunks.push(c); } Defensive patterns
Strategy: validation
Validate before calling
if (!Number.isSafeInteger(index) || index < 0) throw new Error('export cursor must be a non-negative safe integer');
const chunk = world.recordingChunk(index); Type guard
function isValidExportCursor(i: unknown): i is number {
return typeof i === 'number' && Number.isSafeInteger(i) && i >= 0;
} Try / catch
try {
const chunk = world.recordingChunk(Number(cursor));
} catch (e) {
if (e.message === 'Invalid pet export cursor.') {
cursor = 0; // restart pagination from a known-good cursor
} else throw e;
} Prevention
- Always start pagination at index 0 and stop at null.
- Coerce cursor with Number() when it comes from JSON or storage.
- Loop rather than computing the last chunk index yourself.
When it happens
Trigger: Calling world.recordingChunk(-1) or recordingChunk(1.5), or passing a non-numeric index (e.g. a parsed string) as the cursor.
Common situations: Parsing the next-chunk pointer out of JSON and forgetting Number(); decrementing a cursor that reached 0; arithmetic on chunk counters producing floats.
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@433685b202 (2026-09-15).
Data as JSON: /api/errors/174c74192fd8666e.
Report an issue: GitHub.
Appendix: source
Thrown at pet/src/core/pet-world.ts:179
c.petCheckpointVersion = 2; c.historyStart = this.tick; c.hasTelemetry = this.hasTelemetry;
c.bucketIndex -= dropTape; c.interactionIndex = 0;
c.history = new PetWorld(points, tape, interactions, this.sim.expressionVersion, true).historyDigest();
const next = PetWorld.restore(points, tape, interactions, c); next.origin = next.checkpoint();
let committed = false;
return { recording: next.recording(), archive: this.recording(true, true), commit: () => {
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: number, completed = false): string | null {
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;
}
/** Prefix states preserve the original FNV checksum byte for byte. Live
* appends and replacements hash only the changed suffix, so checkpointing
* does not rescan hours of accepted telemetry on the world worker. */View on GitHub (pinned to 433685b202)