clockworklabs/SpacetimeDB · error

Index `{index_name}` not found in table `{table_full_name}`

Error message

Index `{index_name}` not found in table `{table_full_name}`

What it means

Auto-migrate step RemoveIndex: the index was found in the old def, but scanning the database's table schema (table_schema.indexes) found no index whose index_name equals the namespace-joined stored name. The database does not have the index the plan intends to drop — drift between old def and live DB, or a stored-name/prefix mismatch.

Source

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

                stdb.create_index(tx, index_schema, is_unique)?;
            }
            spacetimedb_schema::auto_migrate::AutoMigrateStep::RemoveIndex(key) => {
                let (namespace, index_name) = key;
                let (_owning_def, table_def) = plan
                    .old
                    .find_storing_table(namespace, index_name)
                    .ok_or_else(|| anyhow::anyhow!("RemoveIndex: `{index_name}` not found in old module def"))?;
                let table_full_name = joined(namespace, &table_def.name);
                let stored_name = namespace.join_raw(&index_name.clone().into());
                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 index_schema = table_schema
                    .indexes
                    .iter()
                    .find(|index| index.index_name == stored_name)
                    .ok_or_else(|| anyhow::anyhow!("Index `{index_name}` not found in table `{table_full_name}`"))?;

                log!(logger, "Dropping index `{index_name}` on table `{table_full_name}`");
                stdb.drop_index(tx, index_schema.index_id)?;
            }
            spacetimedb_schema::auto_migrate::AutoMigrateStep::ChangeTableAccessorName(table_name_key) => {
                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!("ChangeTableAccessorName: `{table_name}` not found in new module def")
                    })?;

                let table_id = stdb.table_id_from_name_mut(tx, &table_name)?.unwrap();
                let new_alias = namespace.join(new_table_def.accessor_name.clone());

                log!(
                    logger,
                    "Changing table accessor name for `{table_name}` to `{new_alias}`",

View on GitHub (pinned to 6dee26c6ef)

Solutions

  1. Refresh the old def from the database's actual schema and regenerate the plan
  2. Compare the stored index_name (with namespace prefix and alias applied) against the plan's name to spot prefixing mismatches
  3. For disposable databases, republish fresh instead of reconciling drift

Example fix

// before: drop straight from the plan's key
let index_schema = table_schema.indexes.iter()
    .find(|i| i.index_name == stored_name)
    .ok_or_else(|| anyhow!("Index `{index_name}` not found in table `{table_full_name}`"))?;

// after: resolve by column set when the prefixed name is uncertain
let index_schema = table_schema.indexes.iter()
    .find(|i| ColSet::from(i.algorithm.columns()) == index_cols)
    .ok_or_else(|| anyhow!("Index `{index_name}` not found in table `{table_full_name}`"))?;
Defensive patterns

Strategy: validation

Validate before calling

// Before RemoveIndex, confirm the index exists under its stored (prefixed) name
let stored_name = namespace.join_raw(&index_name.clone().into());
anyhow::ensure!(
    stdb.schema_for_table_mut(&mut tx, table_id)?.indexes.iter()
        .any(|i| i.index_name == stored_name),
    "index {index_name} absent from {table_full_name}; regenerate the plan"
);

Type guard

fn index_present(schema: &TableSchema, stored_name: &str) -> bool {
    schema.indexes.iter().any(|i| i.index_name == stored_name)
}

Try / catch

match auto_migrate_database(&stdb, &mut tx, auth, &plan, &logger) {
    Err(e) if e.to_string().contains("not found in table") => {
        // drift or prefix mismatch: compare stored index names before retrying
        for i in &stdb.schema_for_table_mut(&mut tx, table_id)?.indexes {
            log::warn!("stored index: {}", i.index_name);
        }
        anyhow::bail!("{e:#}");
    }
    r => r,
}

Prevention

When it happens

Trigger: The index was already dropped in the database while the old def still lists it; stored name mismatch because submodule index names are stored prefixed (namespace.join_raw) and the lookup key differs; an earlier partially applied migration removed the index.

Common situations: Retrying migrations after partial application; alias/prefix changes for submodule indexes; manual index drops via SQL alongside module-managed indexes.

Related errors


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