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
- Read the full cause chain ({e:#}) — it names the panic or error inside the handler; fix the module code it points to.
- Make the handler total over the harness's minimal request: no unwraps on headers/body, handle empty input explicitly.
- 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.
- 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
- Write handlers total over the harness's minimal GET: never unwrap on headers, body, or query params.
- Return error-status Responses for domain failures instead of panicking or returning Err.
- Unit-test handlers inside the module with an empty-headers/empty-body request before wiring the harness test.
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
- cannot serialize refs without a typespace
- Invalid HTTP method
- unable to decode args
- begin mutable transaction while one is already active
- export is a function with incorrect type
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)