{"record":{"id":"504f615d2bead444","repo":"clockworklabs/SpacetimeDB","slug":"error-skipping-reducer-registration-s-because","errorCode":null,"errorMessage":"ERROR: Skipping reducer registration '%s' because circular reference error is set\n","messagePattern":"ERROR: Skipping 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":331,"sourceCode":"                }\n                column_defaults_by_table_[table_name].push_back(RawColumnDefaultValueV10{i, serialized_value});\n                table_it->default_values = column_defaults_by_table_[table_name];\n                break;\n            }\n        }\n        if (!field_found) {\n            SetConstraintRegistrationError(\n                \"FIELD_NOT_FOUND\",\n                \"table='\" + table_name + \"' default field='\" + field_name + \"' was not found\");\n        }\n    }\n\n    template<typename Func>\n    void RegisterReducer(const std::string& reducer_name,\n                         Func func,\n                         const std::vector<std::string>& param_names) {\n        if (g_circular_ref_error) {\n            std::fprintf(stderr, \"ERROR: Skipping 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":313,"sourceCodeEnd":349,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/524b4487d949b61a07d4f39c862d1290259dfd20/crates/bindings-cpp/include/spacetimedb/internal/v10_builder.h#L313-L349","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Locate the '[CIRCULAR REFERENCE DETECTED]' chain in stderr and break that cycle (store IDs, not embedded types)","Check reducer parameter and row types for members that (transitively) reference the enclosing type","Rebuild and confirm the reducer appears in the module's reducer list on publish","Do not try to reorder declarations — the flag is global for the whole module; only removing the cycle fixes it"],"exampleFix":"// before — recursive payload poisons the module, reducer is skipped\nstruct Msg { std::vector<Msg> replies; };\nST_REDUCTOR(send_msg)(SpacetimeDB::ReducerContext&, Msg m) { /* ... */ }\n// after\nstruct Msg { std::vector<uint64_t> reply_ids; };\nST_REDUCTOR(send_msg)(SpacetimeDB::ReducerContext&, Msg m) { /* ... */ }","handlingStrategy":"validation","validationCode":"// Ensure reducers register: after static init, the flag must be clear\n#include <spacetimedb/internal/module_type_registration.h>\nTEST(Module, ReducersRegistered) {\n    EXPECT_FALSE(SpacetimeDB::Internal::g_circular_ref_error);\n    // additionally assert the reducer appears in the module's reducer list\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep reducer parameter types acyclic","Add a CI test that enumerates expected reducers and compares against the registered module","One type cycle suppresses all later reducer registrations — fix the cycle, not each reducer"],"tags":["spacetimedb","cpp","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"}