{"record":{"id":"b36393f682284e94","repo":"Hmbown/CodeWhale","slug":"invalid-shared-habitat-the-existing-file-was-kept","errorCode":null,"errorMessage":"Invalid shared habitat; the existing file was kept","messagePattern":"Invalid shared habitat; the existing file was kept","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/tui/pet_watch/owner.rs","lineNumber":314,"sourceCode":"    let lock_path = WorkspaceFile::open(&root, Path::new(\"owner.lock\"), true)?;\n    let original = lock_path.open_update(true, false)?;\n    let mut lifetime = fd_lock::RwLock::new(original.try_clone()?);\n    let _guard = lifetime\n        .try_write()\n        .map_err(|_| anyhow::anyhow!(\"Another pet owner is running\"))?;\n    let mut store = Store::at(&root)?;\n    let previous = store.load()?;\n    let mut saved = if let Some(text) = previous {\n        let value: Saved = serde_json::from_str(&text)?;\n        if value.version != 1\n            || uuid::Uuid::parse_str(&value.identity).is_err()\n            || value.token.len() != 64\n            || !value.token.bytes().all(|b| b.is_ascii_hexdigit())\n            || !valid_source(&value.source)\n            || value.clients.len() > MAX_CLIENTS\n            || !value.appearance.valid()\n        {\n            anyhow::bail!(\"Invalid shared habitat; the existing file was kept\");\n        }\n        value\n    } else {\n        Saved {\n            version: 1,\n            identity: uuid::Uuid::new_v4().to_string(),\n            token: format!(\n                \"{}{}\",\n                uuid::Uuid::new_v4().simple(),\n                uuid::Uuid::new_v4().simple()\n            ),\n            port: requested_port,\n            source: \"unattached\".into(),\n            source_revision: 0,\n            cursor: 0,\n            clients: BTreeMap::new(),\n            recording: Value::Null,\n            appearance: Appearance::default(),","sourceCodeStart":296,"sourceCodeEnd":332,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/tui/pet_watch/owner.rs#L296-L332","documentation":"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.","triggerScenarios":"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()`.","commonSituations":"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.","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"],"exampleFix":"// before (hand-edited habitat file)\n{ \"token\": \"abc123\", \"clients\": [ ...dozens... ] }\n// after\nrm ~/.local/share/codewhale/shared-habitat.json  # let serve regenerate it","handlingStrategy":"validation","validationCode":"let s = serde_json::from_slice::<Saved>(&bytes)?;\nif s.token.len() != 64 || !s.token.bytes().all(|b| b.is_ascii_hexdigit()) {\n    eprintln!(\"habitat token invalid; regenerating\");\n}\nif s.clients.len() > MAX_CLIENTS || !s.appearance.valid() {\n    // back up and delete the file before serve\n}","typeGuard":"fn habitat_file_valid(s: &Saved) -> bool {\n    s.token.len() == 64\n        && s.token.bytes().all(|b| b.is_ascii_hexdigit())\n        && valid_source(&s.source)\n        && s.clients.len() <= MAX_CLIENTS\n        && s.appearance.valid()\n}","tryCatchPattern":"match serve(...) {\n    Err(e) if e.to_string().contains(\"Invalid shared habitat\") => {\n        backup_and_remove(habitat_path)?;\n        serve(...).await\n    }\n    r => r,\n}","preventionTips":["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"],"tags":["rust","validation","state-file","tui"],"backgroundTag":"schema-validation-failed","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}