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
- Resolve the target table's id in the same transaction and set constraint.table_id before calling create_constraint
- 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
- Resolve table ids in the same transaction that creates the constraint
- Validate constraint schemas at construction in migration tooling
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
- `table_id` must not be `TableId::SENTINEL` in `{index_schema
- `table_id` must not be `TableId::SENTINEL` in `{seq:#?}`
- `sequence_id` must be `SequenceId::SENTINEL` in `{:#?}`
- unique constraint on table {table_id} column(s) {col_list:?}
- ERROR: Multiple primary keys detected in table '%s'
AI-assisted analysis of clockworklabs/SpacetimeDB@9e0d92412f (2026-08-20).
Data as JSON: /api/errors/15ce8fc02fc9f372.
Report an issue: GitHub.