{"record":{"id":"16e35f4a0496e090","repo":"clockworklabs/SpacetimeDB","slug":"scheduled-table-table-id-doesn-t-have-valid-colu","errorCode":null,"errorMessage":"scheduled table {table_id} doesn't have valid columns","messagePattern":"scheduled table (.+?) doesn't have valid columns","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/core/src/host/scheduler.rs","lineNumber":111,"sourceCode":"\n        // Draining rx before processing schedules from the DB to ensure there are no in-flight messages,\n        // as this can result in duplication.\n        //\n        // Explanation: By this time, if the `Scheduler::schedule` method has been called (the `init` reducer can do that),\n        // there will be an in-flight message in tx that has already been inserted into the DB.\n        // We are building the `queue` below with the DB and then spawning `SchedulerActor`, which will processes\n        // the in-flight message, resulting in a duplicate entry in the queue.\n        while self.rx.try_recv().is_ok() {}\n\n        // Find all Scheduled tables\n        for st_scheduled_row in self.db.iter(&tx, ST_SCHEDULED_ID)? {\n            let table_id = st_scheduled_row.read_col(StScheduledFields::TableId)?;\n            let function_name =\n                Arc::<str>::from(st_scheduled_row.read_col::<Box<str>>(StScheduledFields::ReducerName)?);\n            let (id_column, at_column) = self\n                .db\n                .table_scheduled_id_and_at(&tx, table_id)?\n                .ok_or_else(|| anyhow!(\"scheduled table {table_id} doesn't have valid columns\"))?;\n\n            let now_ts = Timestamp::now();\n            let now_instant = Instant::now();\n\n            // Insert each entry (row) in the scheduled table into `queue`.\n            for scheduled_row in self.db.iter(&tx, table_id)? {\n                let (schedule_id, schedule_at) = get_schedule_from_row(&scheduled_row, id_column, at_column)?;\n                // calculate duration left to call the scheduled reducer\n                let duration = schedule_at.to_duration_from(now_ts);\n                let at = schedule_at.to_timestamp_from(now_ts);\n                let id = ScheduledFunctionId {\n                    table_id,\n                    schedule_id,\n                    id_column,\n                    at_column,\n                };\n                let key = queue.insert_at(\n                    QueueItem::Id {","sourceCodeStart":93,"sourceCodeEnd":129,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/524b4487d949b61a07d4f39c862d1290259dfd20/crates/core/src/host/scheduler.rs#L93-L129","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Republish the module with an SDK version matched to the host so scheduled tables are (re)created with the id/at columns.","If stale st_scheduled rows from an older module persist, clear them or recreate the database, then publish again.","Verify via schema introspection that every scheduled table exposes the generated scheduled_id and scheduled_at columns.","If data must be preserved, write a one-off migration that adds the missing columns before the scheduler starts."],"exampleFix":"// before: table marked scheduled but declared without the SDK macro (no id/at columns)\n#[spacetimedb::table]\npub struct Job { /* ... */ }\n\n// after: use the scheduled-table macro so the id/at columns are generated\n#[spacetimedb::table(scheduled)]\npub struct Job { /* ... */ }","handlingStrategy":"validation","validationCode":"// Before publishing, assert every scheduled table carries the macro-generated columns\nfor table in generated_schema.tables() {\n    if table.is_scheduled {\n        assert!(table.has_col(\"scheduled_id\") && table.has_col(\"scheduled_at\"),\n            \"scheduled table {} lacks generated columns\", table.name);\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Declare scheduled tables only through the SDK's scheduled-table macro.","Never edit st_* system tables manually.","Keep module SDK and host versions in lockstep across upgrades.","Test publishes on a copy of the database after version upgrades."],"tags":["scheduler","scheduled-tables","schema-mismatch","startup"],"backgroundTag":"schema-validation-failed","analyzedSha":"524b4487d949b61a07d4f39c862d1290259dfd20","analyzedAt":"2026-08-16T23:58:54.611Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}