clockworklabs/SpacetimeDB · error
table {} not found in old_module_def
Error message
table {} not found in old_module_def What it means
During update_database, every existing non-system, non-view table in the database must be found by full prefix-qualified name in the old module def used to build the migration plan. The error means the database contains a user table that the supplied old module def does not declare — the def and the actual database schema have drifted apart.
Source
Thrown at crates/engine/src/update.rs:173
// Build a map from full-name (namespaced) -> (owning_def, table_def) covering root and all
// submodule tables. Submodule tables are stored in the DB with prefixed names like
// "lib.library_procedure_timer", but `ModuleDef::table()` only has the current level.
// `all_tables_with_prefix()` returns the owning submodule alongside each def, which is also
// needed so that `check_compatible` resolves column type refs against the correct
// (sub)module typespace.
let old_tables_by_name: std::collections::HashMap<String, _> = old_module_def
.all_tables_with_prefix()
.into_iter()
.map(|(prefix, owning_def, table_def)| (format!("{}{}", prefix, &table_def.name[..]), (owning_def, table_def)))
.collect();
for table in existing_tables
.iter()
.filter(|table| table.table_type != StTableType::System && !table.is_view())
{
let (owning_def, old_def) = old_tables_by_name
.get(table.table_name.as_ref())
.ok_or_else(|| anyhow::anyhow!("table {} not found in old_module_def", table.table_name))?;
table.check_compatible(owning_def, old_def)?;
}
match plan {
MigratePlan::Manual(plan) => manual_migrate_database(stdb, tx, plan, logger),
MigratePlan::Auto(plan) => auto_migrate_database(stdb, tx, auth_ctx, plan, logger),
}
}
/// Manually migrate a database.
fn manual_migrate_database(
_stdb: &RelationalDB,
_tx: &mut MutTxId,
_plan: ManualMigratePlan,
_logger: &dyn UpdateLogger,
) -> anyhow::Result<UpdateResult> {
unimplemented!("Manual database migrations are not yet implemented")View on GitHub (pinned to 6dee26c6ef)
Solutions
- Republish the exact module currently initialized on the database so the old def matches reality before migrating
- Check the failing table's full name for submodule-prefix issues (lib.<submodule>.<table>) on both sides
- If the database is disposable, publish to a fresh database instead of migrating drifted state
Example fix
// before: plan built from a stale cached def
let plan = build_plan(cached_old_def, new_def);
update_database(&stdb, &mut tx, auth, plan, &logger)?;
// after: verify the def covers the DB before planning
let existing: Vec<String> = stdb.get_all_tables_mut(&mut tx)?
.iter().filter(|t| t.table_type != StTableType::System && !t.is_view())
.map(|t| t.table_name.to_string()).collect();
for name in &existing {
anyhow::ensure!(old_tables_by_name.contains_key(name), "table {name} missing from old def");
} Defensive patterns
Strategy: validation
Validate before calling
// Verify every user table in the DB is declared by the old def before migrating
let declared: HashSet<String> = old_module_def
.all_tables_with_prefix()
.into_iter()
.map(|(prefix, _, t)| format!("{}{}", prefix, &t.name[..]))
.collect();
for t in stdb.get_all_tables_mut(&mut tx)? {
if t.table_type != StTableType::System && !t.is_view() {
anyhow::ensure!(declared.contains(t.table_name.as_ref()),
"table {} not covered by old def", t.table_name);
}
} Type guard
fn table_is_module_managed(t: &TableSchema) -> bool {
t.table_type != StTableType::System && !t.is_view()
} Try / catch
match update_database(&stdb, &mut tx, auth, plan, &logger) {
Err(e) if e.to_string().contains("not found in old_module_def") => {
// def/DB drift: refresh the old def or republish fresh; do not retry the same plan
anyhow::bail!("migration aborted; old def does not describe the database: {e:#}");
}
r => r,
} Prevention
- Build migration plans from the def of the module actually published
- Avoid creating tables via raw SQL alongside module tables
- In tests, publish fresh databases instead of migrating drifted fixtures
When it happens
Trigger: Auto-migrating with an old module def that does not match the module actually published on the database; submodule table-name prefix mismatches (stored names look like lib.<submodule>.<table> while the def only has the local name); tables created outside the module (raw SQL DDL) that no module def covers.
Common situations: Version skew between a host's cached module def and the DB; republishing after submodule renames; mixing manual SQL DDL tables with module tables in one database.
Related errors
- Precheck failed: added sequence {sequence_name} refers to un
- table should exist in the database for AddConstraint
- expected ModuleDef to contain key, but it does not
- When replaying `st_column` update: `table_primary_key` shoul
- When replaying `st_column` update: `table_type` should not h
AI-assisted analysis of clockworklabs/SpacetimeDB@6dee26c6ef (2026-08-20).
Data as JSON: /api/errors/4e395415357d6a22.
Report an issue: GitHub.