clockworklabs/SpacetimeDB · error
TableId `{return_id}` does not exist
Error message
TableId `{return_id}` does not exist What it means
After the subscription's return TableId is resolved, compile_subscription looks up the schema for that id via tx.schema_for_table(return_id). A None result means no table with that id exists in the database schema, so the query is rejected.
Source
Thrown at crates/query/src/lib.rs:42
const MAX_SQL_LENGTH: usize = 50_000;
pub fn compile_subscription(
sql: &str,
tx: &impl SchemaView,
auth: &AuthCtx,
) -> Result<(Vec<ProjectPlan>, TableId, TableName, bool)> {
if sql.len() > MAX_SQL_LENGTH {
bail!("SQL query exceeds maximum allowed length: \"{sql:.120}...\"")
}
let (plan, mut has_param) = parse_and_type_sub(sql, tx, auth)?;
let Some(return_id) = plan.return_table_id() else {
bail!("Failed to determine TableId for query")
};
let Some(return_name) = tx.schema_for_table(return_id).map(|schema| schema.table_name.clone()) else {
bail!("TableId `{return_id}` does not exist")
};
// Resolve any RLS filters
let plan_fragments = resolve_views_for_sub(tx, plan, auth, &mut has_param)?
.into_iter()
.map(compile_select)
.collect::<Vec<_>>();
// Does this subscription read from a client-specific view?
// If so, it is as if the view is parameterized by `:sender`.
// We must know this in order to generate the correct query hash.
let reads_view = plan_fragments.iter().any(|plan| plan.reads_from_view(false));
Ok((plan_fragments, return_id, return_name, has_param || reads_view))
}
/// A utility for parsing and type checking a sql statement
pub fn compile_sql_stmt(sql: &str, tx: &impl SchemaView, auth: &AuthCtx) -> Result<Statement> {View on GitHub (pinned to 6dee26c6ef)
Solutions
- Re-fetch the schema on the client (regenerate bindings / re-describe the database) and re-subscribe using the new table name.
- Verify the table exists: spacetetime describe <db> or spacetime sql <db> "SELECT * FROM ..." against the target table.
- Re-subscribe only after the publish/migration has fully completed.
Example fix
# before: stale table name from old bindings subscription = await db.subscription "SELECT * FROM old_table" # after: regenerate bindings, use current table name spacetime generate --lang typescript --out-dir ./module_bindings subscription = await db.subscription "SELECT * FROM renamed_table"
Defensive patterns
Strategy: retry
Validate before calling
# before (re)subscribing after a publish, confirm the table exists: spacetime describe my-db # or from the client: fetch the database schema and check the table name is present
Try / catch
try {
await db.subscription.build([`SELECT * FROM ${table}`]).subscribe();
} catch (e: any) {
if (String(e.message).includes("does not exist")) {
await db.refreshSchema(); // or regenerate bindings, then retry once
await db.subscription.build([`SELECT * FROM ${newTable}`]).subscribe();
}
} Prevention
- Regenerate client bindings immediately after every publish that changes tables.
- Resubscribe after migrations complete, not concurrently with them.
- Handle subscription errors by re-fetching schema and rebuilding queries.
When it happens
Trigger: Subscribing to a table that was dropped or renamed by a migration, or reusing a cached table reference from an old schema version after the module was republished.
Common situations: Clients holding stale subscriptions or generated bindings across a publish that changed table names; racing a subscribe against an in-flight schema migration.
Related errors
- Precheck failed: added sequence {sequence_name} already has
- Cannot remove table `{table_name}`: table contains data. Cle
- table {} not found in old_module_def
- table should exist in the database for AddConstraint
- Index '${indexLabel}' on table '${tableLabel}' must define a
AI-assisted analysis of clockworklabs/SpacetimeDB@6dee26c6ef (2026-08-20).
Data as JSON: /api/errors/bd236bd2975d31bc.
Report an issue: GitHub.