clockworklabs/SpacetimeDB · error

`table_id` must not be `TableId::SENTINEL` in `{constraint:#

Error message

`table_id` must not be `TableId::SENTINEL` in `{constraint:#?}`

What it means

create_st_constraint (reached through MutTxId::create_constraint) requires the ConstraintSchema's table_id to differ from TableId::SENTINEL. A constraint must attach to a concrete existing table; the sentinel means the field was never populated, and creation is refused before the st_constraint row is inserted.

Source

Thrown at crates/datastore/src/locking_tx_datastore/mut_tx.rs:2256

    /// Requires:
    /// - `constraint.constraint_name` must not be used for any other database entity.
    /// - `constraint.constraint_id == ConstraintId::SENTINEL`.
    /// - `constraint.table_id != TableId::SENTINEL`.
    /// - The caller is responsible for ensuring that the backing indices on
    ///   `ColSet::from(&constraint.data.unique_columns())` already have the correct
    ///   uniqueness — this method does not touch the in-memory index uniqueness.
    ///   Use [`Self::create_constraint`] if the indices need to be converted.
    ///
    /// Ensures:
    /// - The constraint metadata is inserted into the system tables (and other data structures reflecting them).
    /// - The returned ID is unique and is not `ConstraintId::SENTINEL`.
    /// - The `bool` in the return value is `true` iff a new `st_constraint` row was
    ///   inserted (and therefore a `PendingSchemaChange::ConstraintAdded` was pushed).
    ///   It is `false` if an identical row already existed (idempotent re-insertion);
    ///   in that case the schema and pending-changes list are untouched.
    fn create_st_constraint(&mut self, mut constraint: ConstraintSchema) -> Result<(ConstraintId, bool)> {
        if constraint.table_id == TableId::SENTINEL {
            return Err(anyhow::anyhow!("`table_id` must not be `TableId::SENTINEL` in `{constraint:#?}`").into());
        }

        let table_id = constraint.table_id;

        log::trace!(
            "CONSTRAINT CREATING: {} for table: {} and data: {:?}",
            constraint.constraint_name,
            table_id,
            constraint.data
        );

        // Insert the constraint row into `st_constraint`.
        // NOTE: Because `st_constraint` has a unique index on constraint_name,
        // this will fail if the table already exists.
        let constraint_row = StConstraintRow {
            table_id,
            constraint_id: constraint.constraint_id,
            constraint_name: constraint.constraint_name.clone(),

View on GitHub (pinned to 9e0d92412f)

Solutions

  1. Resolve the target table's id in the same transaction and set constraint.table_id before calling create_constraint
  2. Validate schema fields (non-sentinel table_id) at construction time to catch the bug earlier

Example fix

// before
let schema = ConstraintSchema { table_id: TableId::SENTINEL, .. };
let constraint_id = tx.create_constraint(schema)?;

// after
let table_id = tx
    .table_id_from_name("t")
    .ok_or_else(|| anyhow::anyhow!("table t does not exist"))?
    .into();
let schema = ConstraintSchema { table_id, .. };
let constraint_id = tx.create_constraint(schema)?;
Defensive patterns

Strategy: type-guard

Validate before calling

// Resolve the target table before building the constraint schema
let table_id = tx
    .table_id_from_name("t")
    .ok_or_else(|| anyhow::anyhow!("table t does not exist"))?
    .into();
let schema = ConstraintSchema { table_id, .. };

Type guard

fn constraint_schema_has_table(schema: &ConstraintSchema) -> bool {
    schema.table_id != TableId::SENTINEL
}

Try / catch

match tx.create_constraint(schema) {
    Err(e) if e.to_string().contains("table_id` must not be `TableId::SENTINEL``") => {
        // Populate the real table id and retry
    }
    other => other,
}

Prevention

When it happens

Trigger: Calling create_constraint with a ConstraintSchema built with the default/sentinel table_id - e.g. a DDL or migration layer that forgot to resolve the target table id.

Common situations: Programmatic schema construction; constraint schemas built from partial metadata in tools and tests.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@9e0d92412f (2026-08-20). Data as JSON: /api/errors/15ce8fc02fc9f372. Report an issue: GitHub.