Hmbown/CodeWhale · error

Invalid shared habitat; the existing file was kept

Error message

Invalid shared habitat; the existing file was kept

What it means

The shared pet-habitat state file (Saved) failed validation when an owner tried to take over the shared habitat. The library refuses to adopt a corrupted, tampered, or foreign file and instead keeps the existing file untouched, generating a fresh identity instead. It is a defensive validation bail inside `serve` that guards token format, source validity, client limits, and appearance fields.

Solutions

  1. Delete or rename the existing shared-habitat file so `serve` can create a fresh, valid one
  2. Check the token: it must be exactly 64 ASCII hex digits; regenerate it if it was edited or truncated
  3. Trim the `clients` list to at most MAX_CLIENTS entries
  4. Validate the `source` field and `appearance` values against the current schema version

Example fix

// before (hand-edited habitat file)
{ "token": "abc123", "clients": [ ...dozens... ] }
// after
rm ~/.local/share/codewhale/shared-habitat.json  # let serve regenerate it
Defensive patterns

Strategy: validation

Validate before calling

let s = serde_json::from_slice::<Saved>(&bytes)?;
if s.token.len() != 64 || !s.token.bytes().all(|b| b.is_ascii_hexdigit()) {
    eprintln!("habitat token invalid; regenerating");
}
if s.clients.len() > MAX_CLIENTS || !s.appearance.valid() {
    // back up and delete the file before serve
}

Type guard

fn habitat_file_valid(s: &Saved) -> bool {
    s.token.len() == 64
        && s.token.bytes().all(|b| b.is_ascii_hexdigit())
        && valid_source(&s.source)
        && s.clients.len() <= MAX_CLIENTS
        && s.appearance.valid()
}

Try / catch

match serve(...) {
    Err(e) if e.to_string().contains("Invalid shared habitat") => {
        backup_and_remove(habitat_path)?;
        serve(...).await
    }
    r => r,
}

Prevention

When it happens

Trigger: Calling `serve` when an existing shared-habitat file has: a token that is not 64 ASCII hex characters, an invalid `source`, more than MAX_CLIENTS client entries, or an appearance record failing `valid()`.

Common situations: Another Codewhale version wrote a newer/older schema; a user hand-edited the habitat file; a truncated or corrupted file after a crash; a habitat file copied from a different machine or workspace.

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.

Related errors


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

Appendix: source

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

    let lock_path = WorkspaceFile::open(&root, Path::new("owner.lock"), true)?;
    let original = lock_path.open_update(true, false)?;
    let mut lifetime = fd_lock::RwLock::new(original.try_clone()?);
    let _guard = lifetime
        .try_write()
        .map_err(|_| anyhow::anyhow!("Another pet owner is running"))?;
    let mut store = Store::at(&root)?;
    let previous = store.load()?;
    let mut saved = if let Some(text) = previous {
        let value: Saved = serde_json::from_str(&text)?;
        if value.version != 1
            || uuid::Uuid::parse_str(&value.identity).is_err()
            || value.token.len() != 64
            || !value.token.bytes().all(|b| b.is_ascii_hexdigit())
            || !valid_source(&value.source)
            || value.clients.len() > MAX_CLIENTS
            || !value.appearance.valid()
        {
            anyhow::bail!("Invalid shared habitat; the existing file was kept");
        }
        value
    } else {
        Saved {
            version: 1,
            identity: uuid::Uuid::new_v4().to_string(),
            token: format!(
                "{}{}",
                uuid::Uuid::new_v4().simple(),
                uuid::Uuid::new_v4().simple()
            ),
            port: requested_port,
            source: "unattached".into(),
            source_revision: 0,
            cursor: 0,
            clients: BTreeMap::new(),
            recording: Value::Null,
            appearance: Appearance::default(),

View on GitHub (pinned to 73e0f67d83)