clockworklabs/SpacetimeDB · critical
Deletion for non-existent table {table_id:?}... huh?
Error message
Deletion for non-existent table {table_id:?}... huh? What it means
While applying a committed transaction's deletions, the datastore looks up each table targeted by delete operations (get_table_and_blob_store_mut). The Err arm with an empty row_ptrs set is tolerated as a no-op (deleting zero rows from a missing table), but finding actual row pointers to delete in a table absent from the committed state means transaction state is inconsistent, so the code panics ('... huh?'). This is an internal locking-datastore invariant violation, not an API misuse by callers.
Source
Thrown at crates/datastore/src/locking_tx_datastore/committed_state.rs:662
let truncated = table.row_count == 0;
if truncated {
truncates.insert(table_id);
}
}
}
for (table_id, row_ptrs) in delete_tables {
match self.get_table_and_blob_store_mut(table_id) {
Ok((table, blob_store, ..)) => delete_rows(
tx_data,
table_id,
table,
blob_store,
row_ptrs.len(),
row_ptrs.iter(),
truncates,
),
Err(_) if !row_ptrs.is_empty() => panic!("Deletion for non-existent table {table_id:?}... huh?"),
Err(_) => {}
}
}
// Delete all tables marked for deletion.
// The order here does not matter as once a `table_id` has been dropped
// it will never be re-created.
for change in pending_schema_changes {
if let PendingSchemaChange::TableRemoved(table_id, mut table) = change {
let row_ptrs = table.scan_all_row_ptrs();
truncates.insert(table_id);
delete_rows(
tx_data,
table_id,
&mut table,
&mut self.blob_store,
row_ptrs.len(),
row_ptrs.into_iter(),View on GitHub (pinned to 6dee26c6ef)
Solutions
- Reproduce with a minimal reducer sequence (create/insert/delete/drop on one table) and report upstream with the panic backtrace.
- Upgrade to the latest spacetimedb patch release, where invariant fixes land.
- Work around by separating schema changes from data mutations: stop writers, apply the drop, then resume.
Defensive patterns
Strategy: try-catch
Try / catch
// isolate engine panics at the task boundary so one bad commit does not kill the worker
let outcome = tokio::task::spawn_blocking(move || run_reducers(db, tx)).await;
match outcome {
Ok(r) => r,
Err(join_err) if join_err.is_panic() => {
// datastore invariant violation: snapshot workload, report upstream, restart worker
report_invariant(join_err.into_panic());
}
Err(e) => return Err(e.into()),
} Prevention
- Keep spacetimedb updated; this panic is an engine bug, not user error.
- Avoid mixing table-drop DDL with concurrent row deletions on the same table.
- Keep a minimal reproducer harness ready to attach to upstream reports.
- Back up commitlogs so any reproduction can be shared safely.
When it happens
Trigger: Committing a MutTx whose delete set references rows in a table that no longer exists in committed state, e.g. interleaved table-drop schema changes with pending row deletions in the same epoch (the code right below also processes pending_schema_changes TableRemoved).
Common situations: Almost always a spacetimedb engine bug; can surface under concurrent reducers mixing DDL (drop/truncate) with row deletion on the same table, or after an upgrade changed schema-change bookkeeping.
Related errors
- cannot deserialize refs without a typespace
- length didn't fit in `u32`
- missing system table sequence id for sequence {} of table {}
- system tables cannot have schedules, but table {}: {sch:?}
- Someone put a forbidden variant in the system table!
AI-assisted analysis of clockworklabs/SpacetimeDB@6dee26c6ef (2026-08-20).
Data as JSON: /api/errors/fbebe1bfd9e304ff.
Report an issue: GitHub.