Hmbown/CodeWhale · error · Error
Pet expression version does not match its checkpoint.
Error message
Pet expression version does not match its checkpoint.
What it means
Every checkpoint in a recording (start and checkpoint) carries its own sim.expressionVersion, defaulting to 1 for legacy data. fromRecording requires each checkpoint's version to match the recording's top-level expressionVersion. A mismatch means the checkpoint and the recording envelope came from different producers or formats, so restoring would mix incompatible simulation state.
Solutions
- Make expressionVersion and every checkpoint's sim.expressionVersion agree (use the checkpoint's version if it is the genuine one).
- Re-export the recording (recordingChunk/prepareSegment) from a single app version so envelope and checkpoint versions match.
- Restore checkpoints only from recordings produced by the same binary that made them.
- If the checkpoint is legacy data, re-record the segment rather than patching versions by hand.
Example fix
// before r.expressionVersion = 2; // envelope upgraded, checkpoint still v1 const world = PetWorld.fromRecording(points, r); // throws // after r.checkpoint.sim.expressionVersion = 2; // migrate the checkpoint too // or better: keep both at their true version r.expressionVersion = r.checkpoint.sim.expressionVersion; const world = PetWorld.fromRecording(points, r);
Defensive patterns
Strategy: validation
Validate before calling
const version = r.expressionVersion ?? 1;
for (const c of [r.start, r.checkpoint])
if (c && (c.sim?.expressionVersion ?? 1) !== version)
throw new Error('Recording and checkpoint expression versions disagree'); Try / catch
try {
world = PetWorld.fromRecording(points, r);
} catch (err) {
if (err.message === 'Pet expression version does not match its checkpoint.')
console.error('Mixed-version recording; re-export from one app version.');
else throw err;
} Prevention
- Always export start/checkpoint and the envelope in the same app version.
- When migrating a recording's version, migrate its checkpoints' sim.expressionVersion too.
- Never copy checkpoints between recordings of different versions.
- Assert version consistency right after parse, before restore.
When it happens
Trigger: Calling `PetWorld.fromRecording(points, r)` where r.expressionVersion is, say, 2, but r.start.sim.expressionVersion or r.checkpoint.sim.expressionVersion is 1 (or absent, defaulting to 1), or vice versa.
Common situations: Stitching a checkpoint from an old recording into a new one; a producer bug that stamped the envelope version but not the checkpoint sim; partially migrated recordings from an app upgrade; hand-editing one of the two version fields.
Related errors
- Invalid legacy pet recording.
- Pet expression version does not match its checkpoint.
- Unsupported pet expression version.
- Automation run schema v
- Automation schema v is newer than supported v
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/badc7099b652abfc.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/tui/pet_watch/pet-native.js:256
const inputEnd = completed ? this.interactionIndex : this.interactionLog.length;
const history = this.historyDigest(tapeEnd, inputEnd);
return { petReplayVersion: this.segmented ? 2 : 1, expressionVersion: this.sim.expressionVersion,
tape: this.tapeLog.slice(0, tapeEnd), interactions: this.interactionLog.slice(0, inputEnd).map(e => ({ ...e })),
...(this.origin ? { start: { ...structuredClone(this.origin), hasTelemetry: this.hasTelemetry, history } } : {}),
...(withCheckpoint ? { checkpoint: { ...this.checkpoint(), history } } : {}) };
}
static fromRecording(points, value) {
const r = value;
if (!r || ![1, 2].includes(r.petReplayVersion) || !Array.isArray(r.tape) || !Array.isArray(r.interactions))
throw new Error('Invalid pet recording.');
const version = r.expressionVersion === undefined ? 1 : r.expressionVersion;
if (![1, 2].includes(version))
throw new Error('Unsupported pet expression version.');
if (r.checkpoint !== undefined && !r.checkpoint || r.start !== undefined && !r.start)
throw new Error('Invalid pet checkpoint.');
for (const c of [r.start, r.checkpoint])
if (c && (c.sim?.expressionVersion ?? 1) !== version)
throw new Error('Pet expression version does not match its checkpoint.');
if (r.petReplayVersion === 1 && (r.start || r.checkpoint && r.checkpoint.petCheckpointVersion !== 1))
throw new Error('Invalid legacy pet recording.');
if (r.petReplayVersion === 2 && (!r.start || r.start.petCheckpointVersion !== 2 || r.start.historyStart !== r.start.tick))
throw new Error('The recording segment is missing its starting checkpoint.');
const first = r.start && PetWorld.restore(points, r.tape, r.interactions, r.start);
const world = r.checkpoint ? PetWorld.restore(points, r.tape, r.interactions, r.checkpoint) : first ?? new PetWorld(points, r.tape, r.interactions, version);
if (first) {
if (!world.segmented || world.tick < first.tick || r.checkpoint && r.checkpoint.historyStart !== first.tick)
throw new Error('Pet segment checkpoints do not agree.');
world.origin = first.checkpoint();
}
return world;
}
/** Retire only consumed input. The exact origin makes each archived segment
* independently replayable; no particle, random stream or score is reset. */
prepareSegment() {
const dropTape = Math.max(0, this.bucketIndex), dropInputs = this.interactionIndex, previous = this.origin;
const tape = this.tapeLog.slice(dropTape), interactions = this.interactionLog.slice(dropInputs);View on GitHub (pinned to 73e0f67d83)