clockworklabs/SpacetimeDB · critical

scheduled table {table_id} doesn't have valid columns

Error message

scheduled table {table_id} doesn't have valid columns

What it means

During SchedulerActor initialization the host iterates every row of the st_scheduled system table and asks the database for each table's scheduled id/at columns via table_scheduled_id_and_at. If that lookup returns None — the table registered as scheduled does not carry the injected scheduled_id/scheduled_at columns — initialization aborts with this error. Scheduled tables get those columns from the SDK's scheduled-table macro, so a None result means the on-disk schema does not look like a properly compiled scheduled table.

Source

Thrown at crates/core/src/host/scheduler.rs:111

        // Draining rx before processing schedules from the DB to ensure there are no in-flight messages,
        // as this can result in duplication.
        //
        // Explanation: By this time, if the `Scheduler::schedule` method has been called (the `init` reducer can do that),
        // there will be an in-flight message in tx that has already been inserted into the DB.
        // We are building the `queue` below with the DB and then spawning `SchedulerActor`, which will processes
        // the in-flight message, resulting in a duplicate entry in the queue.
        while self.rx.try_recv().is_ok() {}

        // Find all Scheduled tables
        for st_scheduled_row in self.db.iter(&tx, ST_SCHEDULED_ID)? {
            let table_id = st_scheduled_row.read_col(StScheduledFields::TableId)?;
            let function_name =
                Arc::<str>::from(st_scheduled_row.read_col::<Box<str>>(StScheduledFields::ReducerName)?);
            let (id_column, at_column) = self
                .db
                .table_scheduled_id_and_at(&tx, table_id)?
                .ok_or_else(|| anyhow!("scheduled table {table_id} doesn't have valid columns"))?;

            let now_ts = Timestamp::now();
            let now_instant = Instant::now();

            // Insert each entry (row) in the scheduled table into `queue`.
            for scheduled_row in self.db.iter(&tx, table_id)? {
                let (schedule_id, schedule_at) = get_schedule_from_row(&scheduled_row, id_column, at_column)?;
                // calculate duration left to call the scheduled reducer
                let duration = schedule_at.to_duration_from(now_ts);
                let at = schedule_at.to_timestamp_from(now_ts);
                let id = ScheduledFunctionId {
                    table_id,
                    schedule_id,
                    id_column,
                    at_column,
                };
                let key = queue.insert_at(
                    QueueItem::Id {

View on GitHub (pinned to 524b4487d9)

Solutions

  1. Republish the module with an SDK version matched to the host so scheduled tables are (re)created with the id/at columns.
  2. If stale st_scheduled rows from an older module persist, clear them or recreate the database, then publish again.
  3. Verify via schema introspection that every scheduled table exposes the generated scheduled_id and scheduled_at columns.
  4. If data must be preserved, write a one-off migration that adds the missing columns before the scheduler starts.

Example fix

// before: table marked scheduled but declared without the SDK macro (no id/at columns)
#[spacetimedb::table]
pub struct Job { /* ... */ }

// after: use the scheduled-table macro so the id/at columns are generated
#[spacetimedb::table(scheduled)]
pub struct Job { /* ... */ }
Defensive patterns

Strategy: validation

Validate before calling

// Before publishing, assert every scheduled table carries the macro-generated columns
for table in generated_schema.tables() {
    if table.is_scheduled {
        assert!(table.has_col("scheduled_id") && table.has_col("scheduled_at"),
            "scheduled table {} lacks generated columns", table.name);
    }
}

Prevention

When it happens

Trigger: A database where a table is marked scheduled in st_scheduled but its schema lacks the schedule id/at columns: modules compiled by an outdated or mismatched SDK, hand-crafted schemas that skipped the macro-generated columns, or leftover system-table state after an incompatible module update.

Common situations: Upgrading host or SDK across versions where scheduled-column generation changed; publishing a module built with a different SDK than the one that created the database; restoring a database from a backup made by an older build.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@524b4487d9 (2026-08-16). Data as JSON: /api/errors/16e35f4a0496e090. Report an issue: GitHub.