clockworklabs/SpacetimeDB · critical

ERROR: HTTP handler must be registered before it is referenc

Error message

ERROR: HTTP handler must be registered before it is referenced by a router

What it means

LookupHttpHandlerName() scans g_http_handlers (filled by RegisterHttpHandlerHandler) for the handler symbol a Router route references; when no entry matches it prints this message and calls std::abort() during module preinit. HTTP handler registration must execute before the router that references it, because ordering is driven by the numeric prefixes of the generated __preinit__NN_* export functions.

Source

Thrown at crates/bindings-cpp/src/internal/Module.cpp:132

    
    // Register a procedure handler (called by V9Builder during registration)
    void RegisterProcedureHandler(const std::string& name,
                                  std::function<std::vector<uint8_t>(ProcedureContext&, BytesSource)> handler) {
        g_procedure_handlers.push_back({name, handler});
    }

    void RegisterHttpHandlerHandler(const std::string& name,
                                    HttpHandlerSymbol handler_symbol,
                                    std::function<HttpResponse(HandlerContext&, HttpRequest)> handler) {
        g_http_handlers.push_back({name, handler_symbol, handler});
    }

    std::string LookupHttpHandlerName(HttpHandlerSymbol handler_symbol) {
        auto it = std::find_if(g_http_handlers.begin(), g_http_handlers.end(), [&](const auto& existing) {
            return existing.symbol == handler_symbol;
        });
        if (it == g_http_handlers.end()) {
            fprintf(stderr, "ERROR: HTTP handler must be registered before it is referenced by a router\n");
            std::abort();
        }
        return it->name;
    }
    
    // Get the number of registered view handlers
    size_t GetViewHandlerCount() {
        return g_view_handlers.size();
    }
    
    size_t GetAnonymousViewHandlerCount() {
        return g_view_anon_handlers.size();
    }
    
    // Get the number of registered procedure handlers
    size_t GetProcedureHandlerCount() {
        return g_procedure_handlers.size();
    }

View on GitHub (pinned to 6dee26c6ef)

Solutions

  1. Move the HTTP handler registration macro so it is registered before the router references it (earlier in the same translation unit, or via a smaller __preinit__ sequence number)
  2. Declare the handler and its registration in the same header that defines the router, so include order enforces registration order
  3. Verify the handler symbol name used by the route exactly matches the registered handler symbol

Example fix

// before: router preinit runs before handler registration
ROUTER(my_router);        // references get_user handler
HTTP_HANDLER(get_user);   // registered too late -> abort

// after
HTTP_HANDLER(get_user);   // register handler first
ROUTER(my_router);        // then reference it
Defensive patterns

Strategy: validation

Validate before calling

# verify preinit ordering in the built module before publishing
# nm <module.wasm> | grep __preinit | sort
# handler registration exports must sort before the router registration export
nm my_module.wasm | grep '__preinit' | sort

Prevention

When it happens

Trigger: A route references a handler whose HTTP_HANDLER registration macro expands to a preinit function that runs after the router's preinit; handler and router split across translation units with unfavorable preinit numbering; the handler symbol referenced by the route differs from the one actually registered.

Common situations: Moving handler macros into a separate header/file during refactoring so their __preinit__ ordering lands after the router's; hand-writing router registration instead of using the provided macros that guarantee ordering.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@6dee26c6ef (2026-08-20). Data as JSON: /api/errors/057d1179324fa430. Report an issue: GitHub.