{"record":{"id":"b24145964d2de692","repo":"Hmbown/CodeWhale","slug":"session-runtime-ownership-changed-reopen-the-session-before","errorCode":null,"errorMessage":"Session Runtime ownership changed; reopen the session before saving","messagePattern":"Session Runtime ownership changed; reopen the session before saving","errorType":"exception","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/session_manager.rs","lineNumber":1826,"sourceCode":"        // resurrect a missing binding nor replace a different recovered owner.\n        // An adoptable empty store (#6207) counts as abandonable on either\n        // side, exactly like a missing one: there is no durable work to lose\n        // in either direction.\n        if let Some(incoming) = session.metadata.runtime_store.as_ref()\n            && let Ok(persisted) =\n                Self::load_session_metadata(&self.validated_session_path(&session.metadata.id)?)\n            && let Some(binding) = persisted.runtime_store\n            && incoming != &binding\n        {\n            let incoming_abandonable = incoming.is_missing_session_store().unwrap_or(false)\n                || incoming.is_adoptable_empty_store().unwrap_or(false);\n            if incoming_abandonable && binding.validate_existing_store().is_ok() {\n                session.metadata.runtime_store = Some(binding);\n            } else {\n                let persisted_abandonable = binding.is_missing_session_store().unwrap_or(false)\n                    || binding.is_adoptable_empty_store().unwrap_or(false);\n                if !persisted_abandonable {\n                    return Err(io::Error::new(\n                        io::ErrorKind::PermissionDenied,\n                        \"Session Runtime ownership changed; reopen the session before saving\",\n                    ));\n                }\n            }\n        }\n        Ok(())\n    }\n\n    /// Save a session to disk using atomic write (temp file + fsync + rename).\n    ///\n    /// Borrowing form: clones once so the ~150 existing `&session` call sites\n    /// keep working. The debounced persistence path already owns its value and\n    /// calls [`Self::save_session_owned`] instead (#6214 T3).\n    pub fn save_session(&self, session: &SavedSession) -> std::io::Result<PathBuf> {\n        self.save_session_owned(session.clone())\n    }\n","sourceCodeStart":1808,"sourceCodeEnd":1844,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/session_manager.rs#L1808-L1844","documentation":"While hydrating a recovered Runtime binding during a save, the manager detected that persisted Runtime ownership no longer matches what the live session holds, and the persisted state is not a safely abandonable/adoptable store. It returns PermissionDenied to stop the save, because writing now could clobber a store owned by another process.","triggerScenarios":"Calling save on a session whose metadata.runtime_store differs from the persisted binding, when the incoming binding is not abandonable-with-valid-store and the persisted binding is neither a missing session store nor an adoptable empty store.","commonSituations":"The same session was opened in two windows/processes; a crashed run left stale ownership records; the session directory was copied or restored while a session was live.","solutions":["Reopen the session (close and reopen it in the UI/CLI) so the manager re-reads current Runtime ownership before saving","Check for another live process holding this session (the boot-owner records) and close it first","If no other process exists and the store is genuinely orphaned, repair or remove the stale ownership record, then reopen and save"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// check for another live owner before saving\nlet owners = load_session_boot_owners();\nassert!(!owners.iter().any(|(id, _)| id == &my_session_id && boot_id_of(id) != my_boot_id));","typeGuard":null,"tryCatchPattern":"match manager.save(&session) {\n    Err(e) if e.to_string().contains(\"Runtime ownership changed\") => {\n        eprintln!(\"session changed elsewhere; reopening...\");\n        reopen_and_retry();\n    }\n    other => other?,\n}","preventionTips":["Open a given session in only one window at a time","Reopen sessions after a crash instead of saving immediately","Don't copy or restore session directories while a session is live"],"tags":["session","concurrency","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"}