{"record":{"id":"95bbcc3a4cb59f52","repo":"Hmbown/CodeWhale","slug":"owner-lock-was-replaced","errorCode":null,"errorMessage":"Owner lock was replaced","messagePattern":"Owner lock was replaced","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"crates/tui/src/tui/pet_watch/owner.rs","lineNumber":467,"sourceCode":"    let mut audio_error = false;\n    let mut waiting = false;\n    let mut last_save = Instant::now();\n    let mut storage_error = false;\n    let origin = Instant::now();\n    let initial_time: f64 = context.with(|ctx| ctx.eval(\"JSON.parse(pet.snapshot()).timeMs\"))?;\n    let mut last = origin;\n    let mut ticks = 0u64;\n    let mut measurements = VecDeque::<f64>::new();\n    save(&context, &mut saved, &mut store)?;\n    let _ = ready.send(Ok(()));\n    loop {\n        *deadline\n            .lock()\n            .map_err(|_| anyhow::anyhow!(\"Clock lock failed\"))? =\n            Instant::now() + Duration::from_secs(5);\n        let now = Instant::now();\n        if !same_file(&lock_path.open_update(false, false)?, original)? {\n            anyhow::bail!(\"Owner lock was replaced\");\n        }\n        if producer\n            .as_ref()\n            .is_some_and(|(_, _, seen, _)| now.duration_since(*seen) > LEASE)\n        {\n            producer = None;\n            waiting = false;\n            context.with(|ctx| ctx.eval::<(), _>(\"pet.disconnectEngine()\"))?;\n        }\n        if audio\n            .as_ref()\n            .is_some_and(|(_, seen)| now.duration_since(*seen) > LEASE)\n        {\n            audio = None;\n        }\n        let elapsed = now.duration_since(last).as_secs_f64();\n        if elapsed >= 1.0 / 30.0 {\n            // A suspended machine advances a bounded amount and marks a gap;","sourceCodeStart":449,"sourceCodeEnd":485,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/tui/pet_watch/owner.rs#L449-L485","documentation":"During the pet-watch owner's periodic world update (`run_world`), the code re-opens the lock file and compares it against the original lock contents. If the file no longer matches, another process has replaced the owner lock, so this holder stops rather than acting as a stale owner. It is an ownership-continuity invariant, not an I/O failure.","triggerScenarios":"Calling `run_world` (via `serve`) after another process deleted and recreated the habitat lock file, or wrote different contents into it, so `same_file` fails on the reopened lock.","commonSituations":"Two TUI instances racing for ownership; a user deleting/cleaning temp or state directories while the TUI runs; a crash of a competing owner followed by manual cleanup.","solutions":["Stop the second Codewhale TUI/process that took over the lock, then restart this one so it becomes the fresh owner","Do not delete or rewrite the habitat lock file while a TUI session is running","If stale state persists after crashes, fully exit all instances and remove both the habitat file and lock before restarting"],"exampleFix":"null","handlingStrategy":"try-catch","validationCode":"// before relying on ownership, confirm the lock still matches\nif !same_file(&lock_path.open_update(false, false)?, &original)? {\n    eprintln!(\"lock replaced; restarting as fresh owner\");\n}","typeGuard":null,"tryCatchPattern":"match run_world(...).await {\n    Err(e) if e.to_string().contains(\"Owner lock was replaced\") => {\n        // yield ownership and re-enter serve loop as a new owner\n        restart_as_fresh_owner().await\n    }\n    r => r,\n}","preventionTips":["Don't delete or rewrite lock files while a TUI instance is running","Run only one owner TUI per shared habitat","After a crash, exit all instances and clean both habitat and lock files before restarting"],"tags":["rust","concurrency","file-lock","ownership"],"backgroundTag":"invalid-state-transition","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-22T21:17:16.096Z"}