clockworklabs/SpacetimeDB · error

ERROR: Skipping reducer registration '%s' because circular r

Error message

ERROR: Skipping reducer registration '%s' because circular reference error is set

What it means

RegisterReducer drops a client-callable reducer because g_circular_ref_error is set — a type cycle was detected while building the module's typespace. The reducer's static_asserts (first parameter must be ReducerContext) are not the problem; the guard fires before them because the module is already poisoned. The module will fail in preinit_99 validation, so the missing reducer is one of several cascading losses.

Source

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

                }
                column_defaults_by_table_[table_name].push_back(RawColumnDefaultValueV10{i, serialized_value});
                table_it->default_values = column_defaults_by_table_[table_name];
                break;
            }
        }
        if (!field_found) {
            SetConstraintRegistrationError(
                "FIELD_NOT_FOUND",
                "table='" + table_name + "' default field='" + field_name + "' was not found");
        }
    }

    template<typename Func>
    void RegisterReducer(const std::string& reducer_name,
                         Func func,
                         const std::vector<std::string>& param_names) {
        if (g_circular_ref_error) {
            std::fprintf(stderr, "ERROR: Skipping reducer registration '%s' because circular reference error is set\n",
                        reducer_name.c_str());
            return;
        }
        using traits = function_traits<Func>;
        static_assert(traits::arity > 0, "Reducer must have at least one parameter (ReducerContext)");
        if constexpr (traits::arity > 0) {
            using FirstParamType = std::remove_cv_t<std::remove_reference_t<typename traits::template arg_t<0>>>;
            static_assert(std::is_same_v<FirstParamType, ReducerContext>,
                "First parameter of reducer must be ReducerContext");
        }

        std::function<void(ReducerContext&, BytesSource)> handler;
        if constexpr (traits::arity == 1) {
            handler = [func](ReducerContext& ctx, BytesSource) {
                auto result = func(ctx);
                if (result.is_err()) {
                    ::SpacetimeDB::fail_reducer(result.error());
                }

View on GitHub (pinned to 524b4487d9)

Solutions

  1. Locate the '[CIRCULAR REFERENCE DETECTED]' chain in stderr and break that cycle (store IDs, not embedded types)
  2. Check reducer parameter and row types for members that (transitively) reference the enclosing type
  3. Rebuild and confirm the reducer appears in the module's reducer list on publish
  4. Do not try to reorder declarations — the flag is global for the whole module; only removing the cycle fixes it

Example fix

// before — recursive payload poisons the module, reducer is skipped
struct Msg { std::vector<Msg> replies; };
ST_REDUCTOR(send_msg)(SpacetimeDB::ReducerContext&, Msg m) { /* ... */ }
// after
struct Msg { std::vector<uint64_t> reply_ids; };
ST_REDUCTOR(send_msg)(SpacetimeDB::ReducerContext&, Msg m) { /* ... */ }
Defensive patterns

Strategy: validation

Validate before calling

// Ensure reducers register: after static init, the flag must be clear
#include <spacetimedb/internal/module_type_registration.h>
TEST(Module, ReducersRegistered) {
    EXPECT_FALSE(SpacetimeDB::Internal::g_circular_ref_error);
    // additionally assert the reducer appears in the module's reducer list
}

Prevention

When it happens

Trigger: An ST_REDUCTOR/RegisterReducer registration object is instantiated (static init) for a module that also contains a self-referential type — typically a reducer parameter or row type that transitively contains itself. The type registrar hits the cycle first and sets the global flag; every subsequent reducer registration prints this and returns.

Common situations: Adding a new reducer that takes a recursive payload struct; a schema refactor that introduces mutual references between row types in a module that already has reducers; static-init ordering differences between translation units making the flag set before reducer registration runs.

Related errors


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