clockworklabs/SpacetimeDB · critical
Router registration failed: %s
Error message
Router registration failed: %s
What it means
The HTTP Router (unstable feature, requires SPACETIMEDB_UNSTABLE_FEATURES) validates every route path at registration time. fail_router_registration prints the specific reason and calls std::abort(), so the process dies during static initialization — before main. Reasons include: a path not starting with '/', a character outside [a-z0-9-_~/] (uppercase and spaces are rejected), nest() into a prefix that existing routes already use, and duplicate method+path pairs.
Source
Thrown at crates/bindings-cpp/include/spacetimedb/router.h:99
}
Router merge(const Router& other) const {
Router merged = *this;
for (const auto& route : other.routes_) {
merged = merged.add_route(route.method, route.path, route.handler_name);
}
return merged;
}
const std::vector<RouteSpec>& routes() const {
return routes_;
}
private:
std::vector<RouteSpec> routes_;
[[noreturn]] static void fail_router_registration(const std::string& message) {
std::fprintf(stderr, "Router registration failed: %s\n", message.c_str());
std::abort();
}
static bool character_is_acceptable_for_route_path(char c) {
return (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '~' || c == '/';
}
static void assert_valid_path(const std::string& path) {
if (!path.empty() && path[0] != '/') {
fail_router_registration("Route paths must start with `/`: " + path);
}
for (char c : path) {
if (!character_is_acceptable_for_route_path(c)) {
fail_router_registration("Route paths may contain only ASCII lowercase letters, digits and `-_~/`: " + path);
}
}
}
View on GitHub (pinned to 524b4487d9)
Solutions
- Read the message text after 'Router registration failed:' — it names the exact rule and offending path
- Normalize paths: start with '/', lowercase only, restricted to a-z 0-9 - _ ~ /
- For nest() collisions, restructure prefixes so no existing route starts with the nest path, or nest before adding overlapping routes
- For duplicates found via routes_overlap, remove or rename one of the same method+path registrations
- Re-run: abort happens at static init, so the module process must simply start cleanly
Example fix
// before — aborts at startup
auto r = SpacetimeDB::Router{}.get("Users", h) // no leading '/', uppercase
.get("/users/:id", h2); // ':' not allowed
// after
auto r = SpacetimeDB::Router{}.get("/users", h)
.get("/users/~id", h2); // valid charset, leading '/' Defensive patterns
Strategy: validation
Validate before calling
// Validate before building the Router (abort() cannot be caught):
#include <cctype>
bool RoutePathIsValid(const std::string& p) {
if (p.empty() || p[0] != '/') return false;
for (char c : p) {
bool ok = (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')
|| c == '-' || c == '_' || c == '~' || c == '/';
if (!ok) return false;
}
return true;
}
// assert RoutePathIsValid(path) for every route in a unit test / startup check Prevention
- Lowercase all route paths and prefix them with '/' by construction (build from constants)
- Reject uppercase, spaces, ':' and '.' in path strings at the source — the router's charset is [a-z0-9-_~/]
- Check nest()/merge() compositions for prefix and method+path overlaps in a unit test, since duplicates abort at static init
- Run the module binary in CI before deploy — the abort happens before main, so a smoke start catches bad routes
When it happens
Trigger: Calling Router::get("users", h) without a leading slash; paths containing uppercase, spaces, dots, or other characters rejected by character_is_acceptable_for_route_path; Router::nest("/api", sub) when routes already beginning with /api exist; merging two routers that both define the same method and path (routes_overlap).
Common situations: Porting REST paths verbatim from another framework that allowed case-insensitive or parameterized paths like '/users/:id' (':' is rejected); composing routers with nest() where prefixes collide; refactoring a route from one method to another while forgetting the old registration; enabling the unstable HTTP feature for the first time on legacy path strings.
Related errors
- ERROR: Skipping multi-column index registration '%s.%s' beca
- ERROR: Skipping default-value registration '%s.%s' because c
- ERROR: Skipping reducer registration '%s' because circular r
- ERROR: Skipping lifecycle reducer registration '%s' because
- ERROR: Skipping view registration '%s' because circular refe
AI-assisted analysis of clockworklabs/SpacetimeDB@524b4487d9 (2026-08-16).
Data as JSON: /api/errors/2d265c925bd6108f.
Report an issue: GitHub.