clockworklabs/SpacetimeDB · error

database ownership changed before reset

Error message

database ownership changed before reset

What it means

`reset_database` re-reads the database from the control DB and asserts the stored `owner_identity` still equals the caller before applying the reset (new program/environment). Because a reset replaces the database's initial program, replicas, and environment, only the current owner may perform it. This guard fires when ownership changed after the earlier lookup or the caller is not the owner.

Solutions

  1. Reset using the identity that currently owns the database (verify owner via database metadata).
  2. Re-authenticate as the original publishing identity if it still owns the database.
  3. Re-create the database under the caller's identity if ownership cannot be recovered, then apply the reset definition to the new database.
  4. Coordinate with the current owner to perform the reset, or have ownership formally reassigned before resetting.

Example fix

// before
await standalone.reset_database(identityA, reset_def) // database now owned by identityB
// after
await standalone.reset_database(current_owner_identity /* identityB */, reset_def)
Defensive patterns

Strategy: validation

Validate before calling

let previous = control_db.get_database_by_identity(&spec.database_identity)?
    .context("database does not exist")?;
if previous.owner_identity != caller_identity {
    // resolve current owner before resetting
    return Err(anyhow!("reset requires owner identity"));
}

Type guard

fn can_reset(db: &Database, caller: &Identity) -> bool { db.owner_identity == *caller }

Prevention

When it happens

Trigger: Calling `reset_database(caller_identity, DatabaseResetDef)` where `previous.owner_identity != caller_identity` — the database was re-published by or transferred to a different identity, or the caller passes a different identity than the one used to publish.

Common situations: Rotated service accounts or CI identities: publish was done with identity A but reset automation runs with identity B; shared team databases where one member re-created the database under their own identity.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@eddf9f5014 (2026-09-20). Data as JSON: /api/errors/d814fe4fcf837f7a. Report an issue: GitHub.

Appendix: source

Thrown at crates/standalone/src/lib.rs:451

        anyhow::ensure!(
            database.owner_identity == *caller_identity,
            "database ownership changed before deletion"
        );
        self.control_db.delete_database(database.id)?;

        for instance in self.control_db.get_replicas_by_database(database.id)? {
            self.delete_replica(instance.id).await?;
        }

        Ok(())
    }

    async fn reset_database(&self, caller_identity: &Identity, spec: DatabaseResetDef) -> anyhow::Result<()> {
        let previous = self
            .control_db
            .get_database_by_identity(&spec.database_identity)?
            .with_context(|| format!("Database `{}` does not exist", spec.database_identity))?;
        anyhow::ensure!(
            previous.owner_identity == *caller_identity,
            "database ownership changed before reset"
        );
        let previous = self.control_db.with_initialization_generation(previous)?;
        let environment = spacetimedb_lib::environment::EnvironmentUpdate {
            values: spec.environment,
            remove: spec.environment_remove,
            replace: spec.environment_replace,
        }
        .resulting_values(&Default::default())?;
        let mut database = previous.clone();
        let program = match spec.program_bytes {
            Some(bytes) => {
                let host_type = spec.host_type.unwrap_or(database.host_type);
                Program::from_bytes(host_type.into(), &bytes[..])
            }
            None => {
                // A reset without an artifact retains the currently committed

View on GitHub (pinned to eddf9f5014)