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

  1. 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).
  2. Clamp/validate the index before calling: if (!Number.isSafeInteger(i) || i < 0) throw or break.
  3. Initialize the cursor variable (a undefined-initialized counter becomes NaN on ++).
  4. 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

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


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)