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

  1. Re-fetch the schema on the client (regenerate bindings / re-describe the database) and re-subscribe using the new table name.
  2. Verify the table exists: spacetetime describe <db> or spacetime sql <db> "SELECT * FROM ..." against the target table.
  3. 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

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


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