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
- Reset using the identity that currently owns the database (verify owner via database metadata).
- Re-authenticate as the original publishing identity if it still owns the database.
- Re-create the database under the caller's identity if ownership cannot be recovered, then apply the reset definition to the new database.
- 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
- Re-authenticate as the publishing identity in CI before reset operations
- Check owner identity from database metadata before every reset
- Avoid mixing team members' identities for publish/reset of shared databases
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
- database ownership changed before deletion
- 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/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 committedView on GitHub (pinned to eddf9f5014)