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

  1. Reopen the session (close and reopen it in the UI/CLI) so the manager re-reads current Runtime ownership before saving
  2. Check for another live process holding this session (the boot-owner records) and close it first
  3. 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

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


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)