clockworklabs/SpacetimeDB · error

ERROR: Skipping schedule registration for table '%s' because

Error message

ERROR: Skipping schedule registration for table '%s' because circular reference error is set

What it means

The V10 builder skips schedule registration (RegisterSchedule, wired from the scheduled-table macro in table_with_constraints.h) because g_circular_ref_error is set — a type cycle was detected during type building. Without this registration the table's scheduled_at column and its associated reducer are never linked, and preinit_99 will fail the module anyway. The schedule itself is fine; the circular type reference elsewhere in the module is the defect.

Source

Thrown at crates/bindings-cpp/include/spacetimedb/internal/v10_builder.h:616

            }(std::make_index_sequence<traits::arity - 1>{}, params, param_names, type_reg);
        }

        RawProcedureDefV10 procedure_def{
            procedure_name,
            std::move(params),
            return_type,
            FunctionVisibility::ClientCallable,
        };
        UpsertProcedure(procedure_def);
    }

    void RegisterHttpHandlerDef(const std::string& handler_name);
    void RegisterHttpRoute(const RawHttpRouteDefV10& route);
    void RegisterHttpRouter(const ::SpacetimeDB::Router& router);

    void RegisterSchedule(const std::string& table_name, uint16_t scheduled_at_column, const std::string& reducer_name) {
        if (g_circular_ref_error) {
            std::fprintf(stderr, "ERROR: Skipping schedule registration for table '%s' because circular reference error is set\n",
                        table_name.c_str());
            return;
        }
        std::optional<std::string> schedule_name = table_name + "_sched";
        auto it = std::find_if(schedules_.begin(), schedules_.end(), [&](const auto& schedule) {
            return schedule.table_name == table_name;
        });
        RawScheduleDefV10 schedule{schedule_name, table_name, scheduled_at_column, reducer_name};
        if (it == schedules_.end()) {
            schedules_.push_back(std::move(schedule));
        } else {
            *it = std::move(schedule);
        }
    }

    void RegisterRowLevelSecurity(const std::string& sql_query) {
        row_level_security_.push_back(RawRowLevelSecurityDefV9{sql_query});
    }

View on GitHub (pinned to 524b4487d9)

Solutions

  1. Break the type cycle shown in the '[CIRCULAR REFERENCE DETECTED]' stderr block
  2. Re-examine scheduled-table row types and their nested members for self-reference
  3. Rebuild and confirm the schedule (table_name + '_sched') appears in the module definition
  4. Note the whole module shares one flag — fixing one cycle re-enables all skipped registrations

Example fix

// before — recursive row type blocks schedule registration
struct Job { uint64_t id; std::optional<Job> parent_job; };
// (table declared scheduled)
// after
struct Job { uint64_t id; std::optional<uint64_t> parent_job_id; };
Defensive patterns

Strategy: validation

Validate before calling

#include <spacetimedb/internal/module_type_registration.h>
TEST(Module, SchedulesRegistered) {
    EXPECT_FALSE(SpacetimeDB::Internal::g_circular_ref_error);
}

Prevention

When it happens

Trigger: A table declared with the scheduled clause (RegisterSchedule(table_name, scheduled_at_column, reducer_name)) exists in a module whose row types contain a cycle. During static init, type registration runs first and sets the global flag; the schedule registration for that table is then printed and skipped.

Common situations: Adding a scheduled table to a schema that already had a recursive struct; cron-style reducers whose payload types were refactored into mutual references; bindings upgrades reordering registration so the flag is set before schedules are registered.

Related errors


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