clockworklabs/SpacetimeDB · error
AddRowLevelSecurity: RLS `{sql_rls}` not found in new module
Error message
AddRowLevelSecurity: RLS `{sql_rls}` not found in new module def What it means
Adding a row-level-security policy: the step carries the RLS SQL text itself as its key, and plan.new.lookup::<RawRowLevelSecurityDefV9>(sql_rls) returns None - the new def set has no RLS definition registered under exactly that SQL string. Any drift in the SQL text (formatting, quoting, normalization) or absence of the RLS in the applied defs makes the lookup miss. The publish aborts.
Source
Thrown at crates/engine/src/update.rs:602
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)?;
}
spacetimedb_schema::auto_migrate::AutoMigrateStep::RemoveRowLevelSecurity(sql_rls) => {
log!(logger, "Removing row-level security `{sql_rls}`");
stdb.drop_row_level_security(tx, sql_rls.clone())?;
}
spacetimedb_schema::auto_migrate::AutoMigrateStep::AddColumns(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!("AddColumns: table `{table_name}` not found in new module def"))?;
let table_id = stdb.table_id_from_name_mut(tx, &table_name).unwrap().unwrap();
let column_schemas = column_schemas_from_defs(owning_def, &table_def.columns, table_id);View on GitHub (pinned to 6dee26c6ef)
Solutions
- Add or change the RLS policy in a dedicated publish with no other schema edits.
- Keep the SQL string byte-identical between publishes when you do not intend to change it.
- Clean-rebuild to eliminate def/plan desync, then republish.
- Align SDK/compiler versions between builds.
- Dev: spacetime publish --delete-data <db>.
- Report if a clean single-change publish still fails.
Example fix
// before: RLS SQL edited together with other schema changes in one publish #[spacetimedb::table(row_level_security = "SELECT * FROM t WHERE owner = :sender")] // ... plus renames/column edits ... // after: two publishes // 1) schema changes only, RLS untouched // 2) update row_level_security alone
Defensive patterns
Strategy: validation
Validate before calling
use spacetimedb_schema::auto_migrate::AutoMigrateStep;
use spacetimedb_schema::raw_def::v9::RawRowLevelSecurityDefV9;
for step in &plan.steps {
if let AutoMigrateStep::AddRowLevelSecurity(sql) = step {
if plan.new.lookup::<RawRowLevelSecurityDefV9>(sql).is_none() {
anyhow::bail!("RLS with SQL {sql:?} not registered in the new def set");
}
}
} Type guard
fn rls_registered(plan: &AutoMigratePlan, sql: &str) -> bool {
plan.new.lookup::<RawRowLevelSecurityDefV9>(sql).is_some()
} Try / catch
match update_database(&stdb, tx, &plan, ...).await {
Err(e) if e.to_string().contains("AddRowLevelSecurity")
&& e.to_string().contains("not found in new module def") => {
// RLS SQL key drift: republish the RLS change alone, SQL byte-identical elsewhere.
}
result => result?,
} Prevention
- Add or edit RLS policies in a dedicated publish with no other schema changes.
- Keep RLS SQL text byte-identical between publishes when not changing it.
- Clean-rebuild before publishing so the planner and applied defs match exactly.
- Align SDK versions to keep SQL serialization stable.
When it happens
Trigger: Editing the RLS SQL text (even whitespace or quote style) while other schema changes share the same publish; the RLS present in the planner's input defs but absent or textually different in the def set actually applied; macro serialization differences across SDK versions.
Common situations: Iterating on RLS expressions alongside table changes; upgrading SDK versions that serialize or normalize RLS SQL differently; bundling RLS edits with renames.
Related errors
- ChangeTableAccessorName: `{table_name}` not found in new mod
- ChangeColumnAccessorName: `{table_name}` not found in new mo
- Column `{col_name}` not found in table `{table_name}`
- ChangeIndexSourceName: `{index_name}` not found in old modul
- ChangeIndexSourceName: `{index_name}` not found in new modul
AI-assisted analysis of clockworklabs/SpacetimeDB@6dee26c6ef (2026-08-20).
Data as JSON: /api/errors/4c702d2d9ab5da29.
Report an issue: GitHub.