clockworklabs/SpacetimeDB · error
database ownership changed before deletion
Error message
database ownership changed before deletion
What it means
`delete_database` verifies that the identity requesting deletion still matches the database's stored `owner_identity` before removing it from the control DB. This TOCTOU guard prevents a caller from deleting a database whose ownership was transferred (or re-created under a different owner) between lookup and deletion. If ownership changed, deletion is refused instead of letting a non-owner destroy the database.
Solutions
- Check `database.owner_identity` via the CLI/API (`spacetime sql` on the database metadata or inspect the publish response) and delete using the identity that currently owns the database.
- Re-authenticate with the credentials of the current owner instead of the original publisher.
- If ownership transfer is intended, complete/verify the transfer first, then retry deletion with the new owner identity.
- If the database identity is ambiguous, confirm you are targeting the correct database identity rather than another owner's database.
Example fix
// before client.delete_database(caller_identity = identityA, database_identity) // after (ownership moved to identityB) client.delete_database(caller_identity = database.owner_identity /* identityB */, database_identity)
Defensive patterns
Strategy: validation
Validate before calling
// before deleting
let database = control_db.get_database_by_identity(&database_identity)?;
if let Some(db) = database {
if db.owner_identity != caller_identity {
// skip or re-authenticate as the current owner
return Err(anyhow!("cannot delete: owned by {}", db.owner_identity));
}
} Type guard
fn can_delete(db: &Database, caller: &Identity) -> bool { db.owner_identity == *caller } Prevention
- Always delete databases with the same identity used to publish them
- Track ownership changes and update automation credentials after transfers
- Cache the owner identity alongside the database identity in test harnesses
When it happens
Trigger: Calling `delete_database(caller_identity, database_identity)` when `database.owner_identity != caller_identity` — i.e. the database was transferred to another owner, re-published by a different identity, or the caller is passing the wrong caller identity / wrong database identity.
Common situations: A developer publishes a database under identity A, transfers or re-publishes it under identity B, then an automation/CI job still holding A's credentials tries to delete it; or a shared test harness (e.g. `finish_module_test`) deletes databases with a stale or different identity than the one used at publish time.
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
- database ownership changed before reset
- mismatched owner identity
- database ownership changed before publication
- {e}
- mismatched database identity
AI-assisted analysis of clockworklabs/SpacetimeDB@eddf9f5014 (2026-09-20).
Data as JSON: /api/errors/6f94aa454a2dc406.
Report an issue: GitHub.
Appendix: source
Thrown at crates/standalone/src/lib.rs:433
spec.host_type,
host.replica_id,
spec.program_bytes.to_vec().into(),
style,
)
.await
}
None => anyhow::bail!(
"Database `{}` does not exist",
spec.database_identity.to_abbreviated_hex()
),
}
}
async fn delete_database(&self, caller_identity: &Identity, database_identity: &Identity) -> anyhow::Result<()> {
let Some(database) = self.control_db.get_database_by_identity(database_identity)? else {
return Ok(());
};
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!(View on GitHub (pinned to eddf9f5014)