Hmbown/CodeWhale · error

Shared habitat could not restore; its file was kept

Error message

Shared habitat could not restore; its file was kept

What it means

During startup the shared habitat's saved state is loaded into the JS runtime and restored via pet.restoreRecording(saved). If that eval fails for any reason, run_world() maps the whole closure error to this message, indicating the saved state file was intentionally kept (not deleted) so restoration can be retried later.

Solutions

  1. Inspect the kept habitat save file for schema drift or corruption and repair or remove it (it is preserved precisely so you can decide)
  2. Downgrade/upgrade Codewhale so the runtime's restoreRecording matches the save's format
  3. Reproduce with verbose JS error output — the anyhow mapping discards the underlying cause, so check logs for the original eval error

Example fix

// before
.map_err(|_| anyhow::anyhow!("Shared habitat could not restore; its file was kept"))?;
// after
.map_err(|e| anyhow::anyhow!("Shared habitat could not restore; its file was kept: {e}"))?;
Defensive patterns

Strategy: validation

Validate before calling

// Validate the save before attempting restore
fn save_is_loadable(text: &str) -> bool {
    serde_json::from_str::<Saved>(text)
        .map(|v| v.version == 1 && uuid::Uuid::parse_str(&v.identity).is_ok())
        .unwrap_or(false)
}

Try / catch

match run_world(&root) {
    Err(e) if e.to_string().contains("could not restore") => {
        // save file was kept; quarantine or repair it, then retry
        archive_save_and_start_fresh(&root)?;
    }
    r => r?,
}

Prevention

When it happens

Trigger: ctx.eval of the restore script fails — e.g. the saved JSON deserialized but does not match what pet.restoreRecording expects (corrupt or schema-drifted save), the JS engine errored, or saved was absent/malformed despite passing the earlier version/uuid checks.

Common situations: A save written by a newer Codewhale version with a changed recording format; truncated or hand-edited habitat state file; JS runtime failure mid-restore after an earlier crash.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/e7182ef2bb1b9f39. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/tui/pet_watch/owner.rs:445

                .map(|line| {
                    line.split_whitespace()
                        .filter_map(|n| n.parse().ok())
                        .collect()
                })
                .collect();
            ctx.globals()
                .set("points", serde_json::to_string(&points).unwrap())?;
            ctx.eval::<(), _>(include_bytes!("pet-native.js").as_slice())?;
            ctx.eval::<(), _>("globalThis.pet = new PetNative(points, '', '[]', true)")?;
            if !saved.recording.is_null() {
                ctx.globals().set("saved", saved.recording.to_string())?;
                ctx.eval::<(), _>(
                    "pet.restoreRecording(saved); pet.resumeEngine(); delete globalThis.saved",
                )?;
            }
            Ok(())
        })
        .map_err(|_| anyhow::anyhow!("Shared habitat could not restore; its file was kept"))?;
    let mut producer: Option<(String, u64, Instant, String)> = None;
    let mut audio: Option<(String, Instant)> = None;
    let mut playback: Option<(super::audio::Output, super::audio_cursor::AudioCursor)> = None;
    let mut audio_error = false;
    let mut waiting = false;
    let mut last_save = Instant::now();
    let mut storage_error = false;
    let origin = Instant::now();
    let initial_time: f64 = context.with(|ctx| ctx.eval("JSON.parse(pet.snapshot()).timeMs"))?;
    let mut last = origin;
    let mut ticks = 0u64;
    let mut measurements = VecDeque::<f64>::new();
    save(&context, &mut saved, &mut store)?;
    let _ = ready.send(Ok(()));
    loop {
        *deadline
            .lock()
            .map_err(|_| anyhow::anyhow!("Clock lock failed"))? =

View on GitHub (pinned to 73e0f67d83)