clockworklabs/SpacetimeDB · error · Error

cannot deserialize refs without a typespace

Error message

cannot deserialize refs without a typespace

What it means

Raised by the SpacetimeDB test harness when the module's HTTP handler itself fails while processing the request. Unlike the route-not-found error, match_http_route succeeded and module.call_http_handler(handler_id, request, body) was invoked, but the WASM handler errored or trapped (panic, host-call failure, bad handler logic). The underlying cause is chained into {e}.

Solutions

  1. Read the full cause chain ({e:#}) — it names the panic or error inside the handler; fix the module code it points to.
  2. Make the handler total over the harness's minimal request: no unwraps on headers/body, handle empty input explicitly.
  3. For expected domain failures, return an HTTP error Response (e.g. 4xx/5xx) and assert on status/body in the test instead of letting the handler fail.
  4. Unit-test the handler inside the module with the same empty-header/empty-body request shape.

Example fix

// before
#[spacetimedb::http_handler]
fn hello(req: Request) -> Response {
    let token = req.header("authorization").unwrap(); // panics under harness GET
    ...
}

// after
#[spacetimedb::http_handler]
fn hello(req: Request) -> Response {
    match req.header("authorization") {
        Some(token) => Response::ok(...),
        None => Response::new(401),
    }
}
Defensive patterns

Strategy: try-catch

Try / catch

match instance.call_http_route_get("/path").await {
    Ok(body) => assert_eq!(body, expected),
    Err(e) if e.to_string().contains("HTTP handler error") => {
        // inspect {e:#} cause chain; print module logs before failing
        panic!("handler trapped - check module-side logs: {e:#}")
    }
    Err(e) => panic!("unexpected: {e:#}"),
}

Prevention

When it happens

Trigger: call_http_route_get("/path") reaches a registered handler that panics (unwrap on None, out-of-bounds), returns an internal Err, or trips a host-side rejection while executing — e.g. code that unwraps request headers or body that the harness's synthetic request does not carry (empty headers, empty body, http://localhost URI).

Common situations: Handler unwraps optional request state the minimal harness request omits; reducer-style code reused in an HTTP handler that assumes client context; bugs that only reproduce under the harness's bare GET request.

Related errors


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

Appendix: source

Thrown at crates/bindings-typescript/src/lib/algebraic_type.ts:170

        return primitiveSerializers[ty.tag];
    }
  },
  /** @deprecated Use `makeSerializer` instead. */
  serializeValue(
    writer: BinaryWriter,
    ty: AlgebraicTypeType,
    value: any,
    typespace?: TypespaceType
  ) {
    AlgebraicType.makeSerializer(ty, typespace)(writer, value);
  },
  makeDeserializer(
    ty: AlgebraicTypeType,
    typespace?: TypespaceType
  ): Deserializer<any> {
    if (ty.tag === 'Ref') {
      if (!typespace)
        throw new Error('cannot deserialize refs without a typespace');
      while (ty.tag === 'Ref') ty = typespace.types[ty.value];
    }
    switch (ty.tag) {
      case 'Product':
        return ProductType.makeDeserializer(ty.value, typespace);
      case 'Sum':
        return SumType.makeDeserializer(ty.value, typespace);
      case 'Array':
        if (ty.value.tag === 'U8') {
          return deserializeUint8Array;
        } else {
          const deserialize = AlgebraicType.makeDeserializer(
            ty.value,
            typespace
          );
          return reader => {
            const length = reader.readU32();
            const result: any[] = Array(length);

View on GitHub (pinned to 6dee26c6ef)