clockworklabs/SpacetimeDB · error

database program changed before publication

Error message

database program changed before publication

What it means

During module publication (update path), the actor re-reads the program hash stored in the ST_MODULE_ID table inside a fresh transaction and asserts it still equals the hash of the currently-installed module. If another writer changed the program between reading the old module info and this transaction, the guard fails with this ensure! message, aborting publication so a concurrent update is not silently overwritten.

Solutions

  1. Retry the publish; the loser should re-fetch the latest module and reapply its changes.
  2. Serialize publishes: only one publisher/CI job per database at a time.
  3. Verify the current module hash on the server matches what you expected before republishing.

Example fix

// before: unserialized concurrent publishes
publish(db, programA); // process 1
publish(db, programB); // process 2 -> ensure! fails

// after: coordinate via expected version / lock
acquire_publish_lock(db);
publish(db, programB);
Defensive patterns

Strategy: retry

Validate before calling

// compare server hash before publishing
let server_hash = client.getModuleHash(db); assert_eq!(server_hash, expected_old_hash, "program changed; re-sync first");

Try / catch

match publish() { Err(e) if e.to_string().contains("program changed") => resync_and_retry(), other => other }

Prevention

When it happens

Trigger: Calling update_database (republish) while another publisher commits a different program hash for the same database before this publication's transaction executes.

Common situations: Two developers running `spacetime publish` for the same database concurrently; an automated pipeline racing a manual publish; publishing from two machines without coordinating.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

Thrown at crates/core/src/host/wasm_common/module_host_actor.rs:740

                return match e {
                    MigrationPolicyError::AutoMigrateFailure(e) => Ok(UpdateDatabaseResult::AutoMigrateError(e.into())),
                    _ => Ok(UpdateDatabaseResult::ErrorExecutingMigration(e.into())),
                }
            }
        };

        let program_hash = program.hash;
        let host_type = HostType::from(program.kind);
        let tx = stdb.begin_mut_tx(IsolationLevel::Serializable, Workload::Internal);
        let (mut tx, _) = stdb.with_auto_rollback(tx, |tx| -> anyhow::Result<()> {
            use spacetimedb_datastore::system_tables::{StModuleFields, ST_MODULE_ID};
            let row = tx
                .iter(ST_MODULE_ID)?
                .next()
                .context("database program is not initialized")?;
            let current_hash =
                spacetimedb_datastore::system_tables::read_hash_from_col(row, StModuleFields::ProgramHash)?;
            anyhow::ensure!(
                current_hash == old_module_info.module_hash,
                "database program changed before publication"
            );
            crate::db::environment::replace(stdb, tx, self.info.module_def.environment(), &environment)?;
            stdb.update_program(tx, program)?;
            Ok(())
        })?;
        system_logger.info(&format!("Updated program to {program_hash}"));

        let auth_ctx = AuthCtx::for_current(replica_ctx.database.owner_identity);
        let res = crate::db::update::update_database(stdb, &mut tx, auth_ctx, plan, system_logger);

        match res {
            Err(e) => {
                // TODO: Review log level after migration/user errors can be distinguished from internal database failures.
                log::warn!("Database update failed: {} @ {}", e, stdb.database_identity());
                system_logger.warn(&format!("Database update failed: {e}"));
                let (_, tx_metrics, reducer) = stdb.rollback_mut_tx(tx);

View on GitHub (pinned to eddf9f5014)