Hmbown/CodeWhale · error · io::Error
Session Runtime ownership changed; reopen the session…
Error message
Session Runtime ownership changed; reopen the session before saving
What it means
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.
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
Defensive patterns
Strategy: retry
Validate before calling
// check for another live owner before saving let owners = load_session_boot_owners(); assert!(!owners.iter().any(|(id, _)| id == &my_session_id && boot_id_of(id) != my_boot_id));
Try / catch
match manager.save(&session) {
Err(e) if e.to_string().contains("Runtime ownership changed") => {
eprintln!("session changed elsewhere; reopening...");
reopen_and_retry();
}
other => other?,
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Cannot open session : its queued input is already open in…
- Cannot open session : its queued input is already open in…
- Owner lock was replaced
- Saved session Runtime store ownership does not match…
- Task execution ownership changed; refusing a stale write
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/b24145964d2de692.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/session_manager.rs:1826
// resurrect a missing binding nor replace a different recovered owner.
// An adoptable empty store (#6207) counts as abandonable on either
// side, exactly like a missing one: there is no durable work to lose
// in either direction.
if let Some(incoming) = session.metadata.runtime_store.as_ref()
&& let Ok(persisted) =
Self::load_session_metadata(&self.validated_session_path(&session.metadata.id)?)
&& let Some(binding) = persisted.runtime_store
&& incoming != &binding
{
let incoming_abandonable = incoming.is_missing_session_store().unwrap_or(false)
|| incoming.is_adoptable_empty_store().unwrap_or(false);
if incoming_abandonable && binding.validate_existing_store().is_ok() {
session.metadata.runtime_store = Some(binding);
} else {
let persisted_abandonable = binding.is_missing_session_store().unwrap_or(false)
|| binding.is_adoptable_empty_store().unwrap_or(false);
if !persisted_abandonable {
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
"Session Runtime ownership changed; reopen the session before saving",
));
}
}
}
Ok(())
}
/// Save a session to disk using atomic write (temp file + fsync + rename).
///
/// Borrowing form: clones once so the ~150 existing `&session` call sites
/// keep working. The debounced persistence path already owns its value and
/// calls [`Self::save_session_owned`] instead (#6214 T3).
pub fn save_session(&self, session: &SavedSession) -> std::io::Result<PathBuf> {
self.save_session_owned(session.clone())
}
View on GitHub (pinned to 73e0f67d83)