Hmbown/CodeWhale · error
Saved session Runtime store ownership does not match…
Error message
Saved session Runtime store ownership does not match; refusing to recover another scope
What it means
validate_existing_store compares the ownership scope recorded in the store's owner file (AGENT_MAIL_OWNER_FILE) against the current execution scope. It throws 'Saved session Runtime store ownership does not match; refusing to recover another scope' when the store belongs to a different session/execution scope. This is a deliberate safety guard against cross-session contamination: one session must never adopt or mutate another session's runtime store.
Solutions
- Verify you are opening the store with the same session/execution scope that created it.
- If the store was copied or restored, remove or fix AGENT_MAIL_OWNER_FILE so it matches the current scope — only if no other session owns it.
- Point the new session at its own fresh data_dir instead of sharing the other scope's store.
- Check for a stale EVENT_TRANSACTION_LOCK_FILE and a live owner process before touching the owner file.
Example fix
// before: sharing one store across sessions --data-dir /shared/runtime-store // owned by another scope // after: per-session store --data-dir /run/app/session-$SESSION_ID/store
Defensive patterns
Strategy: validation
Validate before calling
// before resuming, check the owner file matches this session's scope
let owner: RuntimeStoreOwner =
serde_json::from_str(&std::fs::read_to_string(root.join("AGENT_MAIL_OWNER_FILE"))?)?;
anyhow::ensure!(
runtime_execution_scope(&owner.owner_id, &root.join("EVENT_TRANSACTION_LOCK_FILE")) == my_scope,
"store owned by another scope"
);
Try / catch
match store.validate_existing_store() {
Err(e) if e.to_string().contains("ownership does not match") => {
eprintln!("store belongs to another session; opening a fresh store instead");
open_fresh_store()?;
}
r => r?,
}
Prevention
- Never copy or share a store directory between sessions or machines.
- Give each session its own data_dir (per-session-id path).
- Check for a live owner process before assuming an owner file is stale.
- Restore backups into a new directory, not over a live store.
When it happens
Trigger: Opening a session with a binding whose data_dir contains an owner file whose runtime_execution_scope (owner_id + lock file) differs from the session's current execution_scope — e.g. resuming a copied or shared store directory under a different session id.
Common situations: Cloning or copying a session's runtime store directory to another machine/session; restoring a backup into a live store path; two TUI instances pointed at the same data_dir with different session ids; stale owner file after an unclean shutdown.
Understand the failure class
Background: Conflicting config options: "cannot be used together" — configuration validation errors across open-source libraries — this error's family across 162 libraries.
Related errors
- invalid session id
- invalid session id for memory reconcile
- legacy spillover ownership requires a session id
- Mobile session bootstrap was incomplete
- --resume/--session-id needs a session id, but got an empty…
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/b32aabb0f78d946d.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/runtime_threads.rs:3255
if !self.has_no_durable_work()? {
return Ok(false);
}
if self.has_scope_pinned_automation()? {
return Ok(false);
}
Ok(true)
}
pub(crate) fn validate_existing_store(&self) -> Result<()> {
anyhow::ensure!(
self.data_dir.is_absolute(),
"Saved Runtime store path must be absolute"
);
let root = checked_existing_runtime_store_dir(&self.data_dir)?;
let owner: RuntimeStoreOwner =
serde_json::from_str(&read_store_file(&root.join(AGENT_MAIL_OWNER_FILE))?)?;
validated_record_id(&owner.owner_id, "Runtime owner id")?;
anyhow::ensure!(
runtime_execution_scope(&owner.owner_id, &root.join(EVENT_TRANSACTION_LOCK_FILE))
== self.execution_scope,
"Saved session Runtime store ownership does not match; refusing to recover another scope"
);
Ok(())
}
}
fn runtime_execution_scope(owner_id: &str, event_lock_path: &Path) -> String {
let mut digest = Sha256::new();
digest.update(owner_id.as_bytes());
digest.update([0]);
digest.update(event_lock_path.as_os_str().as_encoded_bytes());
digest
.finalize()
.iter()
.map(|byte| format!("{byte:02x}"))
.collect()View on GitHub (pinned to 73e0f67d83)