Hmbown/CodeWhale · error · Error
Invalid pet export cursor.
Error message
Invalid pet export cursor.
What it means
recordingChunk(index) paginates the recording export into fixed-size slices and validates the cursor: it must be a non-negative safe integer. This error means the export cursor is malformed — negative, fractional, NaN, or exceeding Number.MAX_SAFE_INTEGER.
Solutions
- Fix the driver loop to start at 0 and increment by 1, stopping when the returned chunk no longer ends with a comma (or after the completed=true chunk).
- Clamp/validate the index before calling: if (!Number.isSafeInteger(i) || i < 0) throw or break.
- Initialize the cursor variable (a undefined-initialized counter becomes NaN on ++).
- Coerce external input with Number() and check Number.isSafeInteger before use.
Example fix
// before
for (let i = start; ; i += 0.5) out += world.recordingChunk(i);
// after
for (let i = 0; ; i++) {
const chunk = world.recordingChunk(i);
out += chunk;
if (!chunk.endsWith(',')) { out += world.recordingChunk(i + 1, true); break; }
} Defensive patterns
Strategy: validation
Validate before calling
if (!Number.isSafeInteger(index) || index < 0) throw new Error('cursor must be a non-negative safe integer'); Type guard
const isValidCursor = (i: unknown): i is number => typeof i === 'number' && Number.isSafeInteger(i) && i >= 0;
Try / catch
let i = 0;
try { for (;; i++) { const c = world.recordingChunk(i); if (!c.endsWith(',')) { world.recordingChunk(i + 1, true); break; } } }
catch (e) { if (e.message === 'Invalid pet export cursor.') throw new Error('export loop cursor bug at ' + i); throw e; } Prevention
- Initialize cursor variables (undefined + 1 is NaN).
- Use a canonical driver loop: start at 0, ++i, stop when the chunk lacks a trailing comma.
- Wrap external cursor input with Number.isSafeInteger checks.
When it happens
Trigger: Calling world.recordingChunk(-1), recordingChunk(0.5), recordingChunk(NaN), or passing a non-integer variable (e.g., an uninitialized counter or a string coerced to NaN).
Common situations: An off-by-one loop condition producing a negative index; a driver that computes the next index with broken arithmetic; a driver forgetting the loop should stop when the chunk no longer ends in ',' and instead computing indices past the end incorrectly.
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/a34d23b28c74f882.
Report an issue: GitHub.
Appendix: source
Thrown at pet/ios/Resources/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 433685b202)