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

  1. Start the cursor at 0 and increment by 1 per chunk: `for (let i = 0; ; i++) { const chunk = w.recordingChunk(i); if (!chunk) break; }`.
  2. Guard the index with Number.isSafeInteger(index) && index >= 0 before calling.
  3. Round or floor any computed index before passing it in.
  4. 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

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


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)