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
- 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
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
- 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
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
- ERROR: Skipping multi-column index registration '%s.%s' beca
- ERROR: Skipping default-value registration '%s.%s' because c
- ERROR: Skipping lifecycle reducer registration '%s' because
- ERROR: Skipping view registration '%s' because circular refe
- ERROR: Skipping procedure registration '%s' because circular
AI-assisted analysis of clockworklabs/SpacetimeDB@524b4487d9 (2026-08-16).
Data as JSON: /api/errors/504f615d2bead444.
Report an issue: GitHub.