clockworklabs/SpacetimeDB · error

ChangePrimaryKey: table `{table_name}` not found in new modu

Error message

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

What it means

Applying ChangePrimaryKey, which moves a table's primary key to another column: plan.new.find_table fails, so the new primary key cannot be read from the new def. The primary-key change was bundled with a table key that no longer resolves - plan/def desync. The publish aborts.

Source

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

                let access = if let Some((_owning_def, table_def)) = plan.new.find_table(table_name_key) {
                    table_def.table_access
                } else {
                    let (_owning_def, view_def) = plan.new.find_view(table_name_key).ok_or_else(|| {
                        anyhow::anyhow!("ChangeAccess: `{table_name}` not found as a table or view in new module def")
                    })?;
                    if view_def.is_public {
                        TableAccess::Public
                    } else {
                        TableAccess::Private
                    }
                };
                stdb.alter_table_access(tx, &table_name, access.into())?;
            }
            spacetimedb_schema::auto_migrate::AutoMigrateStep::ChangePrimaryKey(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!("ChangePrimaryKey: table `{table_name}` not found in new module def")
                })?;
                log!(logger, "Changing primary key for table `{table_name}`");
                stdb.alter_table_primary_key(tx, &table_name, table_def.primary_key)?;
            }
            spacetimedb_schema::auto_migrate::AutoMigrateStep::AddSchedule(_) => {
                anyhow::bail!("Adding schedules is not yet implemented");
            }
            spacetimedb_schema::auto_migrate::AutoMigrateStep::RemoveSchedule(_) => {
                anyhow::bail!("Removing schedules is not yet implemented");
            }
            spacetimedb_schema::auto_migrate::AutoMigrateStep::AddRowLevelSecurity(sql_rls) => {
                log!(logger, "Adding row-level security `{sql_rls}`");
                let rls = plan.new.lookup::<RawRowLevelSecurityDefV9>(sql_rls).ok_or_else(|| {
                    anyhow::anyhow!("AddRowLevelSecurity: RLS `{sql_rls}` not found in new module def")
                })?;
                let rls = RowLevelExpr::build_row_level_expr(tx, &auth_ctx, rls)?;

                stdb.create_row_level_security(tx, rls.def)?;

View on GitHub (pinned to 6dee26c6ef)

Solutions

  1. Clean-rebuild and republish.
  2. Publish the primary-key change alone: same table name, same column names.
  3. Pin table and column names with #[table(name = ...)] / #[column(name = ...)].
  4. Align SDK/CLI/server versions with the live database.
  5. Dev: spacetime publish --delete-data <db> (recreating loses rows; export first if needed).
  6. Report as a planner bug if a clean single-change publish fails.

Example fix

// before: change #[primary_key] column AND rename the table in one publish
#[spacetimedb::table(name = "members")] // was `players`
pub struct Member { #[primary_key] pub name: String, pub id: u64 /* was PK */ }

// after: two publishes
// 1) re-key keeping name `players`
#[spacetimedb::table(name = "players")]
pub struct Player { #[primary_key] pub name: String, pub id: u64 }
// 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::ChangePrimaryKey(key) = step {
        if plan.new.find_table(key).is_none() {
            anyhow::bail!("ChangePrimaryKey step references table {:?} missing from new def", key);
        }
    }
}

Type guard

fn pk_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("ChangePrimaryKey") => {
        // Split: re-key with a stable table/column naming; rename in a follow-up publish.
    }
    result => result?,
}

Prevention

When it happens

Trigger: Moving #[primary_key] to a different column while renaming/moving the table, or changing the PK column's stored name, in the same publish; def/plan desync from stale artifacts or version mismatch.

Common situations: Re-keying tables (e.g. from auto_inc id to a composite/natural key) during broader refactors; renaming the PK column in the same commit as the re-key.

Related errors


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