clockworklabs/SpacetimeDB · error

Procedure return value failed to serialize to BSATN

Error message

Procedure return value failed to serialize to BSATN

What it means

When a procedure is called over WebSocket v1 with the binary protocol, the host serializes the return value with `bsatn::to_vec(...).expect("Procedure return value failed to serialize to BSATN")` before sending it only to the caller. BSATN encoding of a well-formed SpacetimeType is essentially infallible, so this panic indicates the return type violates serialization invariants — a hand-written Serialize that errors, or a malformed/mismatched runtime type after schema changes.

Source

Thrown at crates/core/src/client/messages.rs:848

            let status = match status {
                ProcedureStatus::InternalError(msg) => ws_v1::ProcedureStatus::InternalError(msg),
                ProcedureStatus::OutOfEnergy => ws_v1::ProcedureStatus::OutOfEnergy,
                ProcedureStatus::Returned(val) => ws_v1::ProcedureStatus::Returned(serialize_value(val)),
            };
            ws_v1::ServerMessage::ProcedureResult(ws_v1::ProcedureResult {
                status,
                timestamp,
                total_host_execution_duration,
                request_id,
            })
        }

        // Note that procedure returns are sent only to the caller, not broadcast to all subscribers,
        // so we don't have to bother with memoizing the serialization the way we do for reducer args.
        match protocol {
            Protocol::Binary => ws_v1::FormatSwitch::Bsatn(convert(self, |val| {
                bsatn::to_vec(&val)
                    .expect("Procedure return value failed to serialize to BSATN")
                    .into()
            })),
            Protocol::Text => ws_v1::FormatSwitch::Json(convert(self, |val| {
                serde_json::to_string(&SerializeWrapper(val))
                    .expect("Procedure return value failed to serialize to JSON")
                    .into()
            })),
        }
    }
}

View on GitHub (pinned to 524b4487d9)

Solutions

  1. Republish the module so host and schema agree, then regenerate client bindings and reconnect.
  2. Unit-test the return type in the module crate: `bsatn::to_vec(&sample)` should succeed before deploy.
  3. Replace custom Serialize impls on return values with derived SpacetimeType impls.
  4. Align SDK/module/crate versions — version drift can produce malformed types.

Example fix

// before: hand-rolled Serialize on the return type can fail at runtime
fn get_cfg(ctx: &ReducerContext) -> CustomRet { CustomRet::bad_impl() }

// after: derive SpacetimeType and smoke-test encoding
#[derive(spacetimedb::SpacetimeType, Clone)]
pub struct CustomRet { pub level: u8 }

#[test]
fn ret_bsatn_ok() { assert!(spacetimedb_lib::bsatn::to_vec(&CustomRet { level: 3 }).is_ok()); }
Defensive patterns

Strategy: type-guard

Validate before calling

// Module-side test: prove every return value encodes before deploying
#[test]
fn procedure_returns_encode() {
    let sample = make_sample_return();
    assert!(spacetimedb_lib::bsatn::to_vec(&sample).is_ok());
}

Type guard

fn assert_bsatn_serializable<T: spacetimedb_lib::bsatn::Serialize>() {}
// invoke in module init: assert_bsatn_serializable::<MyRet>();

Prevention

When it happens

Trigger: Calling a procedure whose return type fails BSATN encoding: custom Serialize impls returning errors, schema/runtime skew after changing the module without republishing, or SDK/macro version drift producing malformed types.

Common situations: Editing a module's return type and connecting with stale client bindings; hand-rolled Serialize impls on return values; mismatched spacetimedb crate versions between module and SDK.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@524b4487d9 (2026-08-16). Data as JSON: /api/errors/277020a0ef6d67c9. Report an issue: GitHub.