clockworklabs/SpacetimeDB · warning

ERROR: Failed to register named complex type '%s'\n

Error message

ERROR: Failed to register named complex type '%s'\n

What it means

Warning-level failure log in ModuleTypeRegistration::registerTypeByName: after checking the cache and the module's existing type defs, it registers the named complex type via registerType and expects a Ref back; a non-Ref result means the type did not land in the typespace as a named complex type. Unlike the enum path it does not corrupt an index, but the type is missing from the module description, which can surface later as unresolved types or schema mismatches on the server.

Source

Thrown at crates/bindings-cpp/src/internal/module_type_registration.cpp:224

    auto cache_it = type_name_cache_.find(type_name);
    if (cache_it != type_name_cache_.end()) {
        return;
    }
    
    // Check if already registered in module's types array
    for (const auto& type_def : getV10Builder().GetTypeDefs()) {
        if (type_def.source_name.source_name == type_name) {
            uint32_t typespace_index = type_def.ty;
            type_name_cache_[type_name] = typespace_index;
            return;
        }
    }

    // Register by name using the same full-fidelity path as other complex type registration.
    // This avoids lossy conversion of nested fields/variants.
    auto result = registerType(algebraic_type, type_name, cpp_type);
    if (result.get_tag() != AlgebraicType::Tag::Ref) {
        fprintf(stderr, "ERROR: Failed to register named complex type '%s'\n", type_name.c_str());
    }
}

bool ModuleTypeRegistration::isPrimitive(const bsatn::AlgebraicType& type) const {
    auto tag = static_cast<uint32_t>(type.tag());
    // Use range check: String (4) to F64 (19) covers all primitive types
    return tag >= static_cast<uint32_t>(bsatn::AlgebraicTypeTag::String) &&
           tag <= static_cast<uint32_t>(bsatn::AlgebraicTypeTag::F64);
}

bool ModuleTypeRegistration::isSpecialType(const bsatn::AlgebraicType& type) const {
    if (type.tag() != bsatn::AlgebraicTypeTag::Product) {
        return false;
    }
    
    const auto& product = type.as_product();
    if (product.elements.size() != 1) {
        return false;

View on GitHub (pinned to 6dee26c6ef)

Solutions

  1. Rename the type so it cannot collide with built-in/special type names (Identity, ConnectionId, ScheduleAt spellings)
  2. Check for and fix any earlier registration error - the has_error_ state makes registerType return non-Ref
  3. Ensure the algebraic type passed in is genuinely a complex type (Sum/Product), not one that isPrimitive()/isSpecialType() filters out
  4. Verify the module description after publish contains the named type; if not, restructure the field type (e.g. introduce a dedicated struct)

Example fix

// before: field type name collides with a special type name
struct Event { Identity Identity; }; // named like the special type

// after
struct Event { Identity sender; }; // distinct field/type naming
Defensive patterns

Strategy: validation

Prevention

When it happens

Trigger: registerTypeByName called (e.g. from V10Builder::RegisterTable for Sum-typed fields) while an earlier error state is active (dummy U8 returned); the supplied algebraic_type resolves to a primitive or special type (Identity/ConnectionId-shaped products) instead of a complex type; the name is already cached under a different shape.

Common situations: Naming a table's sum-type field identically to a built-in/special type; registering nested variant types while a prior registration already failed; version changes in bindings-cpp altering which types count as 'special'.

Related errors


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