clockworklabs/SpacetimeDB · error

ERROR: Skipping lifecycle reducer registration '%s' because

Error message

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

What it means

A lifecycle reducer (init, client_connected/on-connect, client_disconnected/on-disconnect) registration is skipped because g_circular_ref_error was set by an earlier type-cycle detection. RegisterLifecycleReducer is invoked from the ST_INIT/ST_CLIENT_CONNECTED/ST_CLIENT_DISCONNECTED macros during static init; when the module is already marked broken, the lifecycle hook is silently omitted and preinit_99 will fail the module. The root cause is the circular type reference, not the lifecycle reducer itself.

Source

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

                    p.elements.emplace_back(std::make_optional(param_name), std::move(internal_type));
                }.template operator()<Is>(out_params, names, reg)), ...);
            }(std::make_index_sequence<traits::arity - 1>{}, params, param_names, type_reg);
        }

        RawReducerDefV10 reducer_def{
            reducer_name,
            std::move(params),
            FunctionVisibility::ClientCallable,
            MakeUnitAlgebraicType(),
            MakeStringAlgebraicType(),
        };
        UpsertReducer(reducer_def);
    }

    template<typename Func>
    void RegisterLifecycleReducer(const std::string& reducer_name, Func func, Lifecycle lifecycle) {
        if (g_circular_ref_error) {
            std::fprintf(stderr, "ERROR: Skipping lifecycle 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. Search stderr upwards for '[CIRCULAR REFERENCE DETECTED]' and fix that type first — the lifecycle reducer needs no change
  2. Replace embedded recursive members with ID references in the offending type
  3. Rebuild and verify the init/connect/disconnect reducers are present when the module publishes
  4. If stderr shows multiple skipped registrations, expect them all to clear at once once the cycle is gone

Example fix

// before — module has recursive type; ST_INIT registration is dropped
struct TreeNode { std::vector<TreeNode> children; };
ST_INIT(init_fn)(SpacetimeDB::ReducerContext&) { /* seed data */ }
// after
struct TreeNode { std::vector<uint32_t> child_ids; };
ST_INIT(init_fn)(SpacetimeDB::ReducerContext&) { /* seed data */ }
Defensive patterns

Strategy: validation

Validate before calling

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

Prevention

When it happens

Trigger: A module contains both lifecycle macros and a self/mutually referential type anywhere in its typespace. Static init registers types first; the cycle sets the flag; the subsequent RegisterLifecycleReducer('<name>', ...) call for the init/connect/disconnect hook prints this message and returns without registering.

Common situations: Boilerplate init reducer in a project whose schema was just extended with a recursive struct; porting a Rust module to C++ bindings and keeping a recursive enum/struct shape; adding lifecycle hooks to a module that already failed type registration earlier in the log.

Related errors


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