clockworklabs/SpacetimeDB · error

Column `{col_name}` not found in table `{table_name}`

Error message

Column `{col_name}` not found in table `{table_name}`

What it means

During ChangeColumnAccessorName the engine found the table in the new module def but no column whose name equals the step's col_name (new_table_def.columns lookup by col.name). The planner emitted a column-accessor rename keyed by a stored column name that no longer exists in the new definition. The publish aborts.

Source

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

                log!(
                    logger,
                    "Changing table accessor name for `{table_name}` to `{new_alias}`",
                );
                stdb.alter_table_accessor_name(tx, table_id, new_alias)?;
            }
            spacetimedb_schema::auto_migrate::AutoMigrateStep::ChangeColumnAccessorName(table_name_key, col_name) => {
                let (namespace, local) = table_name_key;
                let table_name = joined(namespace, local);
                let (_, new_table_def): (&ModuleDef, &spacetimedb_schema::def::TableDef) =
                    plan.new.find_table(table_name_key).ok_or_else(|| {
                        anyhow::anyhow!("ChangeColumnAccessorName: `{table_name}` not found in new module def")
                    })?;
                let new_col_def = new_table_def
                    .columns
                    .iter()
                    .find(|col| &col.name == col_name)
                    .ok_or_else(|| anyhow::anyhow!("Column `{col_name}` not found in table `{table_name}`"))?;

                let table_id = stdb.table_id_from_name_mut(tx, &table_name)?.unwrap();
                let table_schema = stdb.schema_for_table_mut(tx, table_id)?;
                let col_schema = table_schema
                    .columns
                    .iter()
                    .find(|col| &col.col_name == col_name)
                    .ok_or_else(|| anyhow::anyhow!("Column `{col_name}` not found in table `{table_name}`"))?;

                log!(
                    logger,
                    "Changing column accessor name for `{}`.`{}` to `{}`",
                    table_name,
                    col_name,
                    new_col_def.accessor_name,
                );
                stdb.alter_column_accessor_name(tx, table_id, col_schema.col_pos, new_col_def.accessor_name.clone())?;
            }

View on GitHub (pinned to 6dee26c6ef)

Solutions

  1. Clean-rebuild (cargo clean -p <crate> or delete target/), spacetime build, republish.
  2. Keep the stored column name pinned (#[spacetimedb::column(name = "hp")]) and rename only the Rust field; publish that alone.
  3. If the column itself must be renamed, do it in its own publish with nothing else changed.
  4. Align SDK/CLI/server versions with the live database's provenance.
  5. Dev only: spacetime publish --delete-data <db>.
  6. Persistent on clean builds: report as an auto_migrate planner bug with the module diff.

Example fix

// before: stored name AND accessor changed in one publish
pub struct Player {
    #[spacetimedb::column(name = "hit_points")] // stored name was `hp`
    pub hp: u32,
}

// after: keep stored name, change accessor only
pub struct Player {
    #[spacetimedb::column(name = "hp")]
    pub hit_points: u32,
}
Defensive patterns

Strategy: validation

Validate before calling

use spacetimedb_schema::auto_migrate::AutoMigrateStep;
for step in &plan.steps {
    if let AutoMigrateStep::ChangeColumnAccessorName(key, col_name) = step {
        if let Some((_, tdef)) = plan.new.find_table(key) {
            anyhow::ensure!(
                tdef.columns.iter().any(|c| &c.name == col_name),
                "column {} missing from new def of table {:?}", col_name, key
            );
        }
    }
}

Type guard

fn column_in_new_def(tdef: &TableDef, col_name: &str) -> bool {
    tdef.columns.iter().any(|c| c.name == col_name)
}

Try / catch

match update_database(&stdb, tx, &plan, ...).await {
    Err(e) if e.to_string().contains("Column `")
        && e.to_string().contains("not found in table")
        && e.to_string().contains("ChangeColumnAccessorName") => {
        // Column key drift: pin #[column(name)], republish the accessor change alone.
    }
    result => result?,
}

Prevention

When it happens

Trigger: Changing #[column(name = ...)] (or deleting the column) in the same publish where the column's accessor changed, so the planner's stored-name key misses in the new def; also column-name normalization/casing drift or submodule namespace differences between the def the planner saw and the def being applied.

Common situations: Renaming struct fields and their stored names in one commit; switching SDK versions that changed name normalization; copy-pasted column attributes after refactors.

Related errors


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