clockworklabs/SpacetimeDB · error

database ownership changed before publication

Error message

database ownership changed before publication

What it means

When updating an existing database, standalone verifies the publisher's identity equals the database's stored owner_identity before applying the new program. If the owner differs — meaning ownership was reassigned or the row changed since the caller last read it — the ensure! aborts the publication with this message so a non-owner cannot overwrite the module.

Solutions

  1. Publish using the identity that owns the database (the original publisher's key/credentials).
  2. If ownership legitimately changed, verify the new owner_identity on the server and publish as that owner.
  3. Transfer or recreate the database under the identity you intend to publish with.

Example fix

// before: publishing with wrong identity
await client.publish(db, program, { identity: devIdentity }); // not owner

// after: publish as owner
await client.publish(db, program, { identity: ownerIdentity });
Defensive patterns

Strategy: validation

Validate before calling

// verify ownership before publishing
const db = await client.getDatabase(dbIdentity);
if (db && db.owner_identity !== myIdentity) throw new Error('not the database owner');

Type guard

const isOwner = (db, identity) => db == null || db.owner_identity === identity;

Try / catch

try { publish(); } catch (e) { if (e.message.includes('ownership changed')) { switchToOwnerCredentials(); retry(); } else throw e; }

Prevention

When it happens

Trigger: Calling publish_database for an existing database with a publisher identity that differs from database.owner_identity, e.g. publishing under a different identity/key after the database was transferred or created by another account.

Common situations: A teammate tries to publish a database owned by another identity; switching local keys/credentials between publishes; automated jobs running with the wrong service 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/c4ceb4f98b2245af. Report an issue: GitHub.

Appendix: source

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

                let program_hash = self.program_store.put(&spec.program_bytes).await?;

                debug_assert_eq!(_hash_for_assert, program_hash);

                let (database, replica) =
                    self.control_db
                        .upsert_database_with_environment(database, None, environment, &[])?;
                // The leader nomination and input are durable already. If this
                // waiter is cancelled, ordinary lookup resumes the same input.
                self.on_insert_replica(&replica).await?;
                debug_assert_eq!(database.id, replica.database_id);

                Ok(None)
            }
            // The database already exists, so we'll try to update it.
            // If that fails, we'll keep the old one.
            Some(database) => {
                anyhow::ensure!(
                    database.owner_identity == *publisher,
                    "database ownership changed before publication"
                );
                let database_id = database.id;
                let database_identity = database.database_identity;

                let leader = self.leader(database_id).await?;
                let update_result = leader
                    .update(
                        database,
                        spec.host_type,
                        spec.program_bytes.to_vec().into(),
                        policy,
                        update,
                        spec.expected_module_version,
                    )
                    .await?;
                if update_result.was_successful() {

View on GitHub (pinned to eddf9f5014)