Hmbown/CodeWhale · error · Error
Saved expression version does not match its checkpoint.
Error message
Saved expression version does not match its checkpoint.
What it means
Thrown when the restored habitat's `expressionVersion` is not 1 or 2, or when a checkpoint exists whose `sim.expressionVersion` (defaulting to 1) does not match the habitat-level `expressionVersion`. The expression state and its checkpoint must agree for a safe restore.
Solutions
- Re-save the habitat so both `expressionVersion` and `checkpoint.sim.expressionVersion` are written consistently.
- Set `checkpoint.sim.expressionVersion` to match the habitat's `expressionVersion` when migrating checkpoints.
- Downgrade/upgrade the saved record to expressionVersion 1 or 2; clear the habitat if unrecoverable.
Example fix
// before h.expressionVersion = 2; // checkpoint still says 1 // after h.expressionVersion = 2; h.checkpoint.sim.expressionVersion = 2;
Defensive patterns
Strategy: validation
Validate before calling
if (![1,2].includes(h.expressionVersion) || (h.checkpoint && (h.checkpoint.sim?.expressionVersion ?? 1) !== h.expressionVersion)) { resaveHabitat(); return; } Type guard
const versionsMatch = (h: any) => [1,2].includes(h.expressionVersion) && (!h.checkpoint || (h.checkpoint.sim?.expressionVersion ?? 1) === h.expressionVersion);
Try / catch
try { restoreHabitat(saved); } catch (e) { if (e.message.includes('expression version')) resetExpressionState(); } Prevention
- Write habitat.expressionVersion and checkpoint.sim.expressionVersion in the same transaction.
- Default both to 1 when migrating older saves.
- Test upgrades from every supported expressionVersion.
When it happens
Trigger: A habitat saved by code that writes `expressionVersion` without updating the checkpoint's `sim.expressionVersion`, or vice versa; a value outside {1, 2}; an undefined checkpoint expression on a v2 habitat without the `?? 1` default applying.
Common situations: Partial migration between expression schema versions, concurrent writers (two tabs with different versions), hand-repaired saves.
Understand the failure class
Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.
Related errors
- Saved habitat is invalid. Save a replay file before…
- A saved trace identifier collided. Try saving again.
- Automation run schema v
- Automation schema v is newer than supported v
- bounded provider catalog cache exceeds its write limit
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/e5c50081436c90da.
Report an issue: GitHub.
Appendix: source
Thrown at pet/src/ui/pet.ts:288
world.step(1 / 30, { motion: !motion.checked, sensitivity: 1 }); seconds = world.frame.timeMs / 1000; play(world.voices); accumulator -= 1 / 30;
}
if ((mode.value === 'live' || !tape.length) && seconds >= duration()) seek.max = String(Math.ceil(seconds / 30) * 30 + 30);
draw();
}
last = now; requestAnimationFrame(animate);
}
try {
const response = await fetch('./whale-points.tsv'); if (!response.ok) throw new Error('Whale point asset is unavailable.');
points = (await response.text()).trim().split('\n').map(row => row.trim().split(/\s+/).map(Number) as [number, number]);
if (points.length !== 980 || points.some(p => p.length !== 2 || !p.every(Number.isFinite))) throw new Error('Invalid whale point asset.');
try {
const saved = await library.getHabitat();
if (saved) {
savedRevision = saved.revision; const h = saved.habitat;
if (h.petPersistenceVersion !== 1 || !Number.isFinite(h.seconds) || h.seconds < 0 || h.seconds > PET_MAX_SECONDS
|| !['wild', 'demo', 'replay'].includes(h.source) || typeof h.still !== 'boolean' || typeof h.sourceName !== 'string') throw new Error('Saved habitat is invalid. Save a replay file before replacing it.');
expressionVersion = h.expressionVersion === undefined ? 1 : h.expressionVersion;
if (![1, 2].includes(expressionVersion) || h.checkpoint && (h.checkpoint.sim?.expressionVersion ?? 1) !== expressionVersion) throw new Error('Saved expression version does not match its checkpoint.');
const savedWorld = PetWorld.fromRecording(points, { ...h, petReplayVersion: h.petReplayVersion === undefined ? 1 : h.petReplayVersion });
if (h.checkpoint && savedWorld.frame.timeMs / 1000 !== h.seconds) throw new Error('Saved pet clock does not match its checkpoint.');
tape = h.tape; interactions = [...h.interactions]; mode.value = h.source; motion.checked = h.still || media.matches;
source.textContent = h.sourceName;
seek.max = String(Math.max(h.source === 'demo' ? 80 : h.source === 'replay' ? 90 : 120, savedWorld.endTimeMs / 1000));
if (h.source === 'replay') {
mode.querySelector<HTMLOptionElement>('[value="replay"]')!.disabled = false;
}
message.textContent = 'Restoring the saved habitat…';
if (h.checkpoint && h.still === motion.checked) adoptWorld(savedWorld);
else await rebuild(h.seconds, undefined, savedWorld.recording(false).start);
message.textContent = 'Habitat restored locally. Sound starts only when you enable it.';
} else await rebuild();
persistenceReady = true; void refreshArchives().catch(() => {});
} catch (error) { persistenceFailed = true; get('persistence').textContent = error instanceof Error ? error.message : 'Local persistence is unavailable.'; await rebuild(); }
void refreshArchives().catch(() => {});
setInterval(() => { void persist(); }, 5000); requestAnimationFrame(animate);
} catch (error) { message.textContent = error instanceof Error ? error.message : 'Unable to start the habitat.'; }View on GitHub (pinned to 433685b202)