clockworklabs/SpacetimeDB · error
Procedure return value failed to serialize to BSATN
Error message
Procedure return value failed to serialize to BSATN
What it means
On WebSocket protocol v2/v3, the module host serializes a successful procedure's return value with `bsatn::to_vec(&return_val).expect("Procedure return value failed to serialize to BSATN")` to build a ProcedureStatus::Returned message. BSATN encoding of a well-formed SpacetimeType is effectively infallible, so this panic signals a malformed return type: a custom Serialize that errors or schema/runtime type drift after changing the module.
Source
Thrown at crates/core/src/host/module_host.rs:2782
}
let ProcedureResultTarget { sender, request_id } = target;
let CallProcedureReturn { result, tx_offset } = ret;
match sender.config.version {
WsVersion::V1 => {
let message = ProcedureResultMessage::from_result(&result, request_id);
self.subscriptions().send_procedure_message(sender, message, tx_offset)
}
WsVersion::V2 | WsVersion::V3 => {
let (status, timestamp, execution_duration) = match result {
Ok(ProcedureCallResult {
return_val,
execution_duration,
start_timestamp,
}) => (
ws_v2::ProcedureStatus::Returned(
bsatn::to_vec(&return_val)
.expect("Procedure return value failed to serialize to BSATN")
.into(),
),
start_timestamp,
TimeDuration::from(execution_duration),
),
Err(err) => (
ws_v2::ProcedureStatus::InternalError(err.to_string().into()),
Timestamp::UNIX_EPOCH,
TimeDuration::ZERO,
),
};
let message = ws_v2::ProcedureResult {
status,
timestamp,
total_host_execution_duration: execution_duration,
request_id,
};View on GitHub (pinned to 524b4487d9)
Solutions
- Republish the module and regenerate client bindings so type definitions match.
- Add a module-side unit test asserting `bsatn::to_vec(&sample_return)` succeeds.
- Use derived SpacetimeType impls; never hand-write Serialize for procedure returns.
- Pin consistent spacetimedb crate/SDK versions.
Example fix
// before: custom Serialize on the return value risks a host panic
impl Serialize for MyRet { fn serialize(...) { ... custom error path ... } }
// after: derive and verify encodability
#[derive(spacetimedb::SpacetimeType, Clone)]
pub struct MyRet { pub items: Vec<Item> }
#[test]
fn ret_encodes() { assert!(spacetimedb_lib::bsatn::to_vec(&MyRet::sample()).is_ok()); } Defensive patterns
Strategy: type-guard
Validate before calling
// Before publishing, assert the return type encodes under BSATN
#[test]
fn returns_bsatn_encode() { assert!(spacetimedb_lib::bsatn::to_vec(&MyRet::sample()).is_ok()); } Type guard
fn assert_bsatn_serializable<T: spacetimedb_lib::bsatn::Serialize>() {} Prevention
- Republish modules and regenerate bindings whenever return types change.
- Never hand-write Serialize for values crossing the WS v2/v3 boundary.
- Keep module and SDK versions aligned.
When it happens
Trigger: A procedure call succeeding on WS v2/v3 whose return value then fails BSATN encoding — hand-written Serialize impls returning errors, or a module republished/edited without regenerating client bindings so runtime type and schema disagree.
Common situations: Module return types changed while old clients stay connected; custom Serialize on return values; SDK/macro version skew producing invalid types.
Related errors
- Procedure return value failed to serialize to BSATN
- Procedure return value failed to serialize to JSON
- cannot serialize refs without a typespace
- could not serialize result: object had neither a `ok` nor an
- Could not serialize sum type; unknown tag ${value.tag}
AI-assisted analysis of clockworklabs/SpacetimeDB@524b4487d9 (2026-08-16).
Data as JSON: /api/errors/316710b34d84bb9f.
Report an issue: GitHub.