{"record":{"id":"efe08059e98410bf","repo":"clockworklabs/SpacetimeDB","slug":"database-program-changed-before-publication","errorCode":null,"errorMessage":"database program changed before publication","messagePattern":"database program changed before publication","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/core/src/host/wasm_common/module_host_actor.rs","lineNumber":740,"sourceCode":"                return match e {\n                    MigrationPolicyError::AutoMigrateFailure(e) => Ok(UpdateDatabaseResult::AutoMigrateError(e.into())),\n                    _ => Ok(UpdateDatabaseResult::ErrorExecutingMigration(e.into())),\n                }\n            }\n        };\n\n        let program_hash = program.hash;\n        let host_type = HostType::from(program.kind);\n        let tx = stdb.begin_mut_tx(IsolationLevel::Serializable, Workload::Internal);\n        let (mut tx, _) = stdb.with_auto_rollback(tx, |tx| -> anyhow::Result<()> {\n            use spacetimedb_datastore::system_tables::{StModuleFields, ST_MODULE_ID};\n            let row = tx\n                .iter(ST_MODULE_ID)?\n                .next()\n                .context(\"database program is not initialized\")?;\n            let current_hash =\n                spacetimedb_datastore::system_tables::read_hash_from_col(row, StModuleFields::ProgramHash)?;\n            anyhow::ensure!(\n                current_hash == old_module_info.module_hash,\n                \"database program changed before publication\"\n            );\n            crate::db::environment::replace(stdb, tx, self.info.module_def.environment(), &environment)?;\n            stdb.update_program(tx, program)?;\n            Ok(())\n        })?;\n        system_logger.info(&format!(\"Updated program to {program_hash}\"));\n\n        let auth_ctx = AuthCtx::for_current(replica_ctx.database.owner_identity);\n        let res = crate::db::update::update_database(stdb, &mut tx, auth_ctx, plan, system_logger);\n\n        match res {\n            Err(e) => {\n                // TODO: Review log level after migration/user errors can be distinguished from internal database failures.\n                log::warn!(\"Database update failed: {} @ {}\", e, stdb.database_identity());\n                system_logger.warn(&format!(\"Database update failed: {e}\"));\n                let (_, tx_metrics, reducer) = stdb.rollback_mut_tx(tx);","sourceCodeStart":722,"sourceCodeEnd":758,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/eddf9f5014579a50d4b67630e28b6e15cad9c4af/crates/core/src/host/wasm_common/module_host_actor.rs#L722-L758","documentation":"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.","triggerScenarios":"Calling update_database (republish) while another publisher commits a different program hash for the same database before this publication's transaction executes.","commonSituations":"Two developers running `spacetime publish` for the same database concurrently; an automated pipeline racing a manual publish; publishing from two machines without coordinating.","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."],"exampleFix":"// before: unserialized concurrent publishes\npublish(db, programA); // process 1\npublish(db, programB); // process 2 -> ensure! fails\n\n// after: coordinate via expected version / lock\nacquire_publish_lock(db);\npublish(db, programB);","handlingStrategy":"retry","validationCode":"// compare server hash before publishing\nlet server_hash = client.getModuleHash(db); assert_eq!(server_hash, expected_old_hash, \"program changed; re-sync first\");","typeGuard":null,"tryCatchPattern":"match publish() { Err(e) if e.to_string().contains(\"program changed\") => resync_and_retry(), other => other }","preventionTips":["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"],"tags":["concurrency","publish","hash-mismatch","module-update"],"backgroundTag":"invalid-state-transition","analyzedSha":"eddf9f5014579a50d4b67630e28b6e15cad9c4af","analyzedAt":"2026-09-20T12:15:59.611Z","contentChangedAt":"2026-09-20T12:15:59.611Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}