clockworklabs/SpacetimeDB · error

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

Error message

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

What it means

Auto-migrate step AddTable: plan.new.find_table(table_name_key) returned None, so the table the plan wants to create is missing from the new module def. The migration plan's steps and the def they were derived from are inconsistent — an internal invariant break, not a user-facing validation error.

Source

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

                let table_id = stdb.table_id_from_name_mut(tx, &table_name)?.unwrap();

                if stdb.table_row_count_mut(tx, table_id).unwrap_or(0) > 0 {
                    anyhow::bail!(
                        "Cannot remove table `{table_name}`: table contains data. \
                         Clear the table's rows (e.g. via a reducer) before removing it from your schema."
                    );
                }

                log!(logger, "Dropping table `{table_name}`");
                stdb.drop_table(tx, table_id)?;
            }
            spacetimedb_schema::auto_migrate::AutoMigrateStep::AddTable(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!("AddTable: table `{table_name}` not found in new module def"))?;
                log!(logger, "Creating table `{table_name}`");
                create_table_from_def_with_prefix(stdb, tx, owning_def, table_def, namespace)?;
            }
            spacetimedb_schema::auto_migrate::AutoMigrateStep::AddView(view_name_key) => {
                let (namespace, local) = view_name_key;
                let view_name = joined(namespace, local);
                let (owning_def, view_def) = plan
                    .new
                    .find_view(view_name_key)
                    .ok_or_else(|| anyhow::anyhow!("AddView: view `{view_name}` not found in new module def"))?;
                create_table_from_view_def_with_prefix(stdb, tx, owning_def, view_def, namespace)?;
            }
            spacetimedb_schema::auto_migrate::AutoMigrateStep::RemoveView(view_name_key) => {
                let (namespace, local) = view_name_key;
                let view_name = joined(namespace, local);
                let view_id = stdb
                    .view_id_from_name_mut(tx, &view_name)?
                    .ok_or_else(|| anyhow::anyhow!("RemoveView: view `{view_name}` not found in database"))?;

View on GitHub (pinned to 6dee26c6ef)

Solutions

  1. Regenerate the migration plan from the exact ModuleDef passed as plan.new
  2. Check the failing table key (namespace, local) matches the def's namespacing
  3. Report upstream with both defs if the plan came from the differ
Defensive patterns

Strategy: try-catch

Try / catch

match auto_migrate_database(&stdb, &mut tx, auth, &plan, &logger) {
    Err(e) if e.to_string().contains("AddTable:") && e.to_string().contains("not found in new module def") => {
        // plan/def desync: regenerate the plan from the same def, then retry once
        let plan2 = MigratePlan::Auto(diff_defs(old_def, new_def)?);
        auto_migrate_database(&stdb, &mut tx, auth, &plan2, &logger)
    }
    r => r,
}

Prevention

When it happens

Trigger: A MigratePlan whose steps were computed against a different def than plan.new; namespace/prefix differences making find_table miss the key; a bug in the schema differ emitting an AddTable step for a table it dropped from the def.

Common situations: Hand-assembled or cached plans reused across def revisions; upstream diff bugs; submodule tables whose namespaced key changed between plan creation and apply.

Related errors


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