clockworklabs/SpacetimeDB · error

ERROR: Invalid type index %u for namespace update (max: %zu)

Error message

ERROR: Invalid type index %u for namespace update (max: %zu)\n

What it means

Printed by ModuleTypeRegistration::updateTypeNameInModule when the type index passed in is >= GetTypeDefs().size(). The index comes from type_name_cache_, populated by registerType()/registerAndGetIndex() with typespace indices, but this function indexes the RawTypeDefV10 array; the update is then silently skipped, so the type keeps its un-namespace-qualified name. It is called from set_type_namespace<T>() in module_type_registration.h:151, which the SPACETIMEDB_NAMESPACE macros invoke during preinit to prepend a namespace to an already-registered type.

Source

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

            }
            desc += "}";
            return desc;
        }
        
        case bsatn::AlgebraicTypeTag::Ref:
            return "Ref(" + std::to_string(type.as_ref()) + ")";
            
        default:
            return "Unknown(tag=" + std::to_string(static_cast<int>(type.tag())) + ")";
    }
}

void ModuleTypeRegistration::updateTypeNameInModule(uint32_t type_index, const std::string& new_name) {
    auto& type_defs = getV10Builder().GetTypeDefs();
    
    // Check if the type index is valid
    if (type_index >= type_defs.size()) {
        fprintf(stderr, "ERROR: Invalid type index %u for namespace update (max: %zu)\n", 
                type_index, type_defs.size());
        return;
    }
    
    // Parse the new name to extract namespace and name parts
    auto [scope, name] = parseNamespaceAndName(new_name);
    
    // Update the type definition's scoped name
    type_defs[type_index].source_name.scope = scope;
    type_defs[type_index].source_name.source_name = name;
    
}

// processOptionInnerType function removed - no longer needed
// Options now use the same LazyTypeRegistrar pattern as other types

} // namespace Internal

View on GitHub (pinned to 6dee26c6ef)

Solutions

  1. Ensure every rebuild path calls ModuleTypeRegistration::clear() and resets the V10 builder's typespace and type-def arrays together, so cached indices never outlive the arrays they index (see initializeModuleTypeRegistration at module_type_registration.cpp:28).
  2. Audit for typespace pushes without a matching GetTypeDefs() push: registerComplexType() pushes both (lines 488-499); any other code pushing into GetTypespace().types directly must push a RawTypeDefV10 too, or indices diverge.
  3. Prefer a name-keyed update: change set_type_namespace/updateTypeNameInModule to locate the type-def by source_name instead of by cached index, which is immune to index divergence.
  4. As a guard, bound-check the index at the call site (module_type_registration.h:151) and fail loudly during preinit instead of silently skipping the namespace qualification.

Example fix

// before (module_type_registration.h, set_type_namespace):
// cached typespace index is used to index the type-def array
updateTypeNameInModule(type_index, qualified_name);

// after: resolve the type-def by name so typespace/type-def index
// divergence cannot produce an out-of-range index
updateTypeNameInModule(original_name, qualified_name);
// (updateTypeNameInModule finds the def via source_name, then rewrites scope/name)
Defensive patterns

Strategy: validation

Validate before calling

// in set_type_namespace (module_type_registration.h), before updating:
const auto& defs = getV10Builder().GetTypeDefs();
if (type_index >= defs.size()) {
    // typespace/type-def index divergence or stale cache: do not silently skip
    fprintf(stderr, "namespace update for %s skipped: stale index %u (defs: %zu)\n",
            original_name.c_str(), type_index, defs.size());
    return;
}

Prevention

When it happens

Trigger: A SPACETIMEDB_NAMESPACE(prefix) macro runs for a type whose cached index no longer matches the type-defs array: typespace and type-def arrays have diverged (typespace entries pushed without a matching RawTypeDefV10), a stale type_name_cache_ left over from a previous module build because clear() was not called while the builder was reset (or vice versa), or any path where the typespace grows faster than GetTypeDefs(). Concretely: registering types directly into the typespace (v10_builder pushes) and then using namespace macros on them, or rebuilding a module in the same process without ModuleTypeRegistration::clear().

Common situations: Unit tests or host tooling that build a module, reset state, and rebuild in one process; mixed registration styles (macro-based registration plus manual typespace pushes); version transitions of bindings-cpp where the cache/type-def bookkeeping changed shape. The visible symptom is the namespace prefix missing from the published schema (type names without 'Namespace.') plus this stderr line.

Related errors


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