clockworklabs/SpacetimeDB · error
environment-only publication requires…
Error message
environment-only publication requires expected_module_version
What it means
The core's `update_module_host` validates that an environment-only publication (identified by an empty `program_bytes`) always carries an `expected_module_version` hash. This hash implements optimistic concurrency: the caller asserts which module version it read the environment for, preventing the environment update from racing with a concurrent module update. Without it, the core refuses the update via `ensure!`.
Solutions
- Fetch the database's current module version/hash first and pass it as `expected_module_version`.
- If you actually need code changes, supply non-empty `program_bytes` so this is a full module update.
- Update the calling client/CLI to a version that includes the module hash in environment-only updates.
- Re-read the database state immediately before the call to get a fresh hash and avoid optimistic-concurrency conflicts.
Example fix
// before host_controller.update_module_host(db, host_type, Vec::new(), policy, env_update, None).await? // after let expected = get_current_module_hash(db).await?; // e.g. from database metadata host_controller.update_module_host(db, host_type, Vec::new(), policy, env_update, Some(expected)).await?
Defensive patterns
Strategy: validation
Validate before calling
// rust caller-side check mirroring the core rule
anyhow::ensure!(
!program_bytes.is_empty() || expected_module_version.is_some(),
"environment-only update requires expected_module_version"
); Try / catch
// rust
if let Err(e) = update_module_host(..., Vec::new(), policy, env, None).await {
if e.to_string().contains("environment-only publication requires") {
let hash = fetch_current_module_hash(db).await?;
update_module_host(..., Vec::new(), policy, env, Some(hash)).await?;
} else { return Err(e); }
} Prevention
- Always read the current module hash immediately before an environment-only update.
- Update API clients/CLIs to versions that populate expected_module_version.
- Treat the hash as an optimistic-lock token: re-fetch it after any concurrent-update conflict.
When it happens
Trigger: Calling `update_module_host` (via the update/publish database path) with empty `program_bytes` (environment-only update) and `expected_module_version: None`; a CLI/server path that constructs an `EnvironmentUpdate` without first fetching the current module hash.
Common situations: Tooling or scripts updating only environment variables for a published database but skipping the step of reading the current module version/hash; API clients written before the expected_module_version requirement was introduced.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
- Environment key is both supplied and removed
- Environment key : config input must be a string, boolean or…
- Environment ' ' cannot use an enum payload
- Environment ' ' must be a string or simple enum
- Environment ' ' needs a nonempty literal union
AI-assisted analysis of clockworklabs/SpacetimeDB@eddf9f5014 (2026-09-20).
Data as JSON: /api/errors/715b4f637f40baf4.
Report an issue: GitHub.
Appendix: source
Thrown at crates/core/src/host/host_controller.rs:606
/// [`Self::get_or_launch_module_host`] for details on what this entails).
///
/// If the host was running, and the update fails, the previous version of
/// the host keeps running.
#[tracing::instrument(level = "trace", skip_all, err)]
#[allow(clippy::too_many_arguments)]
pub async fn update_module_host(
&self,
database: Database,
host_type: HostType,
replica_id: u64,
program_bytes: Box<[u8]>,
policy: MigrationPolicy,
environment: spacetimedb_lib::environment::EnvironmentUpdate,
expected_module_version: Option<spacetimedb_lib::Hash>,
) -> anyhow::Result<UpdateDatabaseResult> {
environment.validate()?;
let environment_only = program_bytes.is_empty();
anyhow::ensure!(
!environment_only || expected_module_version.is_some(),
"environment-only publication requires expected_module_version"
);
let program = Program::from_bytes(host_type.into(), program_bytes);
trace!(
"update module host {}/{}: genesis={} update-to={}",
database.database_identity,
replica_id,
database.initial_program,
program.hash
);
let Ok(mut guard) = self.acquire_write_lock(replica_id).await else {
bail!("unable to lock database {} for update", database.database_identity);
};
// `HostController::clone` is fast,
// as all of its fields are either `Copy` or wrapped in `Arc`.View on GitHub (pinned to eddf9f5014)