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

  1. Verify you are opening the store with the same session/execution scope that created it.
  2. 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.
  3. Point the new session at its own fresh data_dir instead of sharing the other scope's store.
  4. 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

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


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)