clockworklabs/SpacetimeDB · error
NO_SUCH_REDUCER
NO_SUCH_REDUCER
Error message
ERROR: Invalid reducer ID %u (have %zu reducers)\n
What it means
Dispatch guard in Module::__call_reducer__: the host invoked a reducer by numeric ID >= the number of reducer handlers registered at module init (g_reducer_handlers). The handler writes 'Invalid reducer ID: <id>' into the caller's error sink and returns StatusCode::NO_SUCH_REDUCER (999), which the client surfaces as a failed reducer call. It means the server's reducer table (captured at publish time) and the binary's registered handlers disagree.
Source
Thrown at crates/bindings-cpp/src/internal/Module.cpp:474
size_t bytes_to_write = bytes.size();
FFI::bytes_sink_write(sink, bytes.data(), &bytes_to_write);
}
Status Module::__call_reducer__(
uint32_t id,
uint64_t sender_0, uint64_t sender_1, uint64_t sender_2, uint64_t sender_3,
uint64_t conn_id_0, uint64_t conn_id_1,
Timestamp timestamp,
BytesSource args_source,
BytesSink error_sink
) {
// Clear any previous error state
SpacetimeDB::Internal::clear_reducer_error();
// Check if reducer ID is valid
if (id >= g_reducer_handlers.size()) {
fprintf(stderr, "ERROR: Invalid reducer ID %u (have %zu reducers)\n",
id, g_reducer_handlers.size());
// Write error message
std::string error = "Invalid reducer ID: " + std::to_string(id);
WriteBytes(error_sink, std::vector<uint8_t>(error.begin(), error.end()));
return StatusCode::NO_SUCH_REDUCER;
}
// Create reducer context
std::array<uint8_t, 32> sender_bytes{};
// Pack the 4 uint64_t parts into 32 bytes
std::memcpy(sender_bytes.data(), &sender_0, 8);
std::memcpy(sender_bytes.data() + 8, &sender_1, 8);
std::memcpy(sender_bytes.data() + 16, &sender_2, 8);
std::memcpy(sender_bytes.data() + 24, &sender_3, 8);
Identity sender_identity(sender_bytes);
View on GitHub (pinned to 6dee26c6ef)
Solutions
- Republish the module so the server's reducer table matches the binary, then retry the call
- Make every reducer registration macro unconditional (no runtime branches around registration) so the handler count is deterministic
- Check server logs for preinit/registration errors at module startup that could have skipped handlers
- Verify the client calls reducers by the exact registered names and uses freshly generated bindings after schema changes
Example fix
// before
REDUCER(create_user) { /* ... */ }
#ifndef DEBUG
REDUCER(audit_log) { /* ... */ } // conditionally absent -> id shift
#endif
// after
REDUCER(create_user) { /* ... */ }
REDUCER(audit_log) { /* ... */ } // always registered, stable ids Defensive patterns
Strategy: validation
Validate before calling
// post-build test: handler counts must match the expected, published module // assert(Module::GetReducerHandlerCount() == EXPECTED_REDUCERS); // and assert every expected name is present in __describe_module__ output
Prevention
- Never wrap reducer registration macros in runtime conditionals or #ifdefs that vary by build
- Republish the module whenever reducers are added, removed, or reordered; regenerate client bindings
- Add a CI test comparing registered handler counts against a golden module description
When it happens
Trigger: The deployed wasm binary does not match the module description the server dispatches against (partial/failed publish or stale binary); reducer registration macros compiled out or wrapped in runtime conditionals so fewer handlers register; an earlier preinit error aborted part of registration; name-index drift after adding/removing reducers between the client's expectation and the published module.
Common situations: Publishing a new module version while old clients/server metadata cache the previous reducer list; #ifdef-ing out a reducer in one build configuration; init failure swallowed before all __preinit__30_* reducer registration functions ran.
Related errors
- NO_SUCH_VIEW
- NO_SUCH_PROCEDURE
- ERROR: Invalid http handler ID %u (have %zu handlers)\n
- There is already a reducer, procedure, or view with the name
- Table ${tableName} defines a schedule, but it seems like the
AI-assisted analysis of clockworklabs/SpacetimeDB@6dee26c6ef (2026-08-20).
Data as JSON: /api/errors/1f1ff3fecf25116f.
Report an issue: GitHub.