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
- Delete or rename the existing shared-habitat file so `serve` can create a fresh, valid one
- Check the token: it must be exactly 64 ASCII hex digits; regenerate it if it was edited or truncated
- Trim the `clients` list to at most MAX_CLIENTS entries
- 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
- Never hand-edit the shared-habitat state file
- Back up and remove the file after crashes instead of reusing possibly truncated state
- Keep Codewhale versions consistent across machines sharing habitat state
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
- Failed to update setting: invalid ocean treatment
- Failed to update setting: invalid work surface placement
- A pinned task provider requires an explicit model
- agent profile provider cannot be empty
- agent profile provider must be a simple provider id
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)