{"record":{"id":"5076eabcf00702c8","repo":"clockworklabs/SpacetimeDB","slug":"error-skipping-lifecycle-reducer-registration-s","errorCode":null,"errorMessage":"ERROR: Skipping lifecycle reducer registration '%s' because circular reference error is set\n","messagePattern":"ERROR: Skipping lifecycle reducer registration '(.+?)' because circular reference error is set\n","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/bindings-cpp/include/spacetimedb/internal/v10_builder.h","lineNumber":400,"sourceCode":"                    p.elements.emplace_back(std::make_optional(param_name), std::move(internal_type));\n                }.template operator()<Is>(out_params, names, reg)), ...);\n            }(std::make_index_sequence<traits::arity - 1>{}, params, param_names, type_reg);\n        }\n\n        RawReducerDefV10 reducer_def{\n            reducer_name,\n            std::move(params),\n            FunctionVisibility::ClientCallable,\n            MakeUnitAlgebraicType(),\n            MakeStringAlgebraicType(),\n        };\n        UpsertReducer(reducer_def);\n    }\n\n    template<typename Func>\n    void RegisterLifecycleReducer(const std::string& reducer_name, Func func, Lifecycle lifecycle) {\n        if (g_circular_ref_error) {\n            std::fprintf(stderr, \"ERROR: Skipping lifecycle reducer registration '%s' because circular reference error is set\\n\",\n                        reducer_name.c_str());\n            return;\n        }\n        using traits = function_traits<Func>;\n        static_assert(traits::arity > 0, \"Reducer must have at least one parameter (ReducerContext)\");\n        if constexpr (traits::arity > 0) {\n            using FirstParamType = std::remove_cv_t<std::remove_reference_t<typename traits::template arg_t<0>>>;\n            static_assert(std::is_same_v<FirstParamType, ReducerContext>,\n                \"First parameter of reducer must be ReducerContext\");\n        }\n\n        std::function<void(ReducerContext&, BytesSource)> handler;\n        if constexpr (traits::arity == 1) {\n            handler = [func](ReducerContext& ctx, BytesSource) {\n                auto result = func(ctx);\n                if (result.is_err()) {\n                    ::SpacetimeDB::fail_reducer(result.error());\n                }","sourceCodeStart":382,"sourceCodeEnd":418,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/524b4487d949b61a07d4f39c862d1290259dfd20/crates/bindings-cpp/include/spacetimedb/internal/v10_builder.h#L382-L418","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Search stderr upwards for '[CIRCULAR REFERENCE DETECTED]' and fix that type first — the lifecycle reducer needs no change","Replace embedded recursive members with ID references in the offending type","Rebuild and verify the init/connect/disconnect reducers are present when the module publishes","If stderr shows multiple skipped registrations, expect them all to clear at once once the cycle is gone"],"exampleFix":"// before — module has recursive type; ST_INIT registration is dropped\nstruct TreeNode { std::vector<TreeNode> children; };\nST_INIT(init_fn)(SpacetimeDB::ReducerContext&) { /* seed data */ }\n// after\nstruct TreeNode { std::vector<uint32_t> child_ids; };\nST_INIT(init_fn)(SpacetimeDB::ReducerContext&) { /* seed data */ }","handlingStrategy":"validation","validationCode":"#include <spacetimedb/internal/module_type_registration.h>\nTEST(Module, LifecycleReducersRegistered) {\n    EXPECT_FALSE(SpacetimeDB::Internal::g_circular_ref_error);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Verify after publish that init/connect/disconnect hooks are present in the module description","Keep lifecycle macro names and the module's types in one review pass — a schema change can silently drop hooks","Assert the flag is false in tests to catch regressions early"],"tags":["spacetimedb","cpp","lifecycle-reducer","circular-reference","schema","static-initialization"],"backgroundTag":"circular-type-reference","analyzedSha":"524b4487d949b61a07d4f39c862d1290259dfd20","analyzedAt":"2026-08-16T23:58:54.611Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}