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
- Retry the publish; the loser should re-fetch the latest module and reapply its changes.
- Serialize publishes: only one publisher/CI job per database at a time.
- 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
- Serialize publishes per database (single CI lane or lock)
- Re-fetch the current module before republishing
- Use expected_module_version to detect concurrent updates early
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
- unable to lock database
- unable to lock database
- Aborted.
- Aborting because publishing would require manual migration…
- AddColumns: table ` ` not found in new module def
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)