clockworklabs/SpacetimeDB · error

ChangeColumns: table `{table_name}` not found in new module

Error message

ChangeColumns: table `{table_name}` not found in new module def

What it means

Applying AutoMigrateStep::ChangeColumns, which replaces a table's row type after column additions/removals/type changes: plan.new.find_table fails, so the replacement column schemas cannot be built. The step's table key does not resolve in the new module def - plan/def desync. The publish aborts before alter_table_row_type runs.

Source

Thrown at crates/engine/src/update.rs:545

                let table_id = stdb.table_id_from_name_mut(tx, &table_full_name)?.unwrap();
                let table_schema = stdb.schema_for_table_mut(tx, table_id)?;
                let sequence_schema = table_schema
                    .sequences
                    .iter()
                    .find(|sequence| sequence.sequence_name == stored_name)
                    .unwrap();

                log!(
                    logger,
                    "Dropping sequence `{sequence_name}` from table `{table_full_name}`"
                );
                stdb.drop_sequence(tx, sequence_schema.sequence_id)?;
            }
            spacetimedb_schema::auto_migrate::AutoMigrateStep::ChangeColumns(table_name_key) => {
                let (namespace, local) = table_name_key;
                let table_name = joined(namespace, local);
                let (owning_def, table_def) = plan.new.find_table(table_name_key).ok_or_else(|| {
                    anyhow::anyhow!("ChangeColumns: table `{table_name}` not found in new module def")
                })?;
                let table_id = stdb.table_id_from_name_mut(tx, &table_name).unwrap().unwrap();
                let column_schemas = column_schemas_from_defs(owning_def, &table_def.columns, table_id);

                log!(logger, "Changing columns of table `{table_name}`");

                stdb.alter_table_row_type(tx, table_id, column_schemas)?;
            }
            spacetimedb_schema::auto_migrate::AutoMigrateStep::ReschemaEventTable(table_name_key) => {
                let (namespace, local) = table_name_key;
                let table_name = joined(namespace, local);
                let (owning_def, table_def) = plan.new.find_table(table_name_key).ok_or_else(|| {
                    anyhow::anyhow!("ReschemaEventTable: table `{table_name}` not found in new module def")
                })?;
                let table_id = stdb.table_id_from_name_mut(tx, &table_name).unwrap().unwrap();
                let column_schemas = column_schemas_from_defs(owning_def, &table_def.columns, table_id);

                log!(logger, "Changing schema of event table `{}`", table_name);

View on GitHub (pinned to 6dee26c6ef)

Solutions

  1. Clean-rebuild and republish.
  2. Split the publish: apply column changes with the table name/location untouched first, then rename/move.
  3. Pin the stored name with #[table(name = "...")] and keep it stable across the migration.
  4. Align SDK/CLI/server versions with the live database's provenance.
  5. Dev: spacetime publish --delete-data <db>.
  6. Report as a planner bug if a clean split publish still triggers it.

Example fix

// before: rename + column add in one publish
#[spacetimedb::table(name = "members")] // was "players"
pub struct Member { pub level: u32 /* new column */ }

// after: two publishes
// 1) add the column, keep the name
#[spacetimedb::table(name = "players")]
pub struct Player { pub level: u32 }
// 2) rename the table separately
Defensive patterns

Strategy: validation

Validate before calling

use spacetimedb_schema::auto_migrate::AutoMigrateStep;
for step in &plan.steps {
    if let AutoMigrateStep::ChangeColumns(key) = step {
        if plan.new.find_table(key).is_none() {
            anyhow::bail!("ChangeColumns step references table {:?} missing from new def", key);
        }
    }
}

Type guard

fn table_in_new_def(plan: &AutoMigratePlan, key: (&str, &str)) -> bool {
    plan.new.find_table(key).is_some()
}

Try / catch

match update_database(&stdb, tx, &plan, ...).await {
    Err(e) if e.to_string().contains("ChangeColumns")
        && e.to_string().contains("not found in new module def") => {
        // Split: publish column changes with a stable table name; rename afterwards.
    }
    result => result?,
}

Prevention

When it happens

Trigger: Changing a table's columns (types, adds, drops) while renaming, moving, or deleting the table in the same publish; or the def set being applied differs from the one the planner diffed (stale build artifacts, mismatched compiler/SDK versions).

Common situations: Squashed feature branches combining column changes with table renames; CI publishing with a different toolchain; incremental builds after SDK upgrades.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@6dee26c6ef (2026-08-20). Data as JSON: /api/errors/e25f79fb1fdd90cf. Report an issue: GitHub.