clockworklabs/SpacetimeDB · error · DBError::Other

v2 unsubscribe requires binary protocol

Error message

v2 unsubscribe requires binary protocol

What it means

The v2 unsubscribe path with UnsubscribeFlags::SendDroppedRows must evaluate the removed queries and return their rows in the v2 binary format. evaluate_queries returned FormatSwitch::Json, meaning subscription evaluation ran under the JSON format while the client negotiated v2 BSATN. The v2 QueryRows structure cannot be built from JSON updates, so the unsubscribe fails.

Source

Thrown at crates/core/src/subscription/module_subscription_actor.rs:1026

        };

        let mut_tx = ScopeGuard::<MutTxId, _>::into_inner(mut_tx);
        let (mut tx, tx_offset) = self.unsubscribe_views_and_downgrade_tx(mut_tx, &removed_queries, auth.caller())?;

        let (rows, metrics) = if request.flags == ws_v2::UnsubscribeFlags::SendDroppedRows {
            let (update, metrics) = return_on_err!(
                self.evaluate_queries(sender.clone(), &removed_queries, &tx, TableUpdateType::Unsubscribe,),
                send_err_msg,
                (None, false)
            );
            subscription_metrics
                .num_queries_evaluated
                .inc_by(removed_queries.len() as _);

            let query_rows = match update {
                ws_v1::FormatSwitch::Bsatn(update) => query_rows_from_update(update, true)?,
                ws_v1::FormatSwitch::Json(_) => {
                    return Err(DBError::Other(anyhow::anyhow!(
                        "v2 unsubscribe requires binary protocol"
                    )))
                }
            };
            (Some(query_rows), Some(metrics))
        } else {
            (None, None)
        };
        if let Some(metrics) = metrics {
            tx.metrics.merge(metrics);
        }

        let _ = self.broadcast_queue.send_client_message_v2(
            sender.clone(),
            Some(tx_offset),
            ws_v2::UnsubscribeApplied {
                request_id: request.request_id,
                query_set_id: request.query_set_id,

View on GitHub (pinned to 9e0d92412f)

Solutions

  1. Upgrade client and server to matching versions that both support the v2 binary protocol
  2. Ensure the connection negotiates the v2 protocol end-to-end so evaluation uses BSATN
  3. For custom builds, verify the subscription actor is configured for BSATN, not JSON, on v2 connections
Defensive patterns

Strategy: validation

Validate before calling

// On the client, confirm the connection negotiated the v2 binary protocol before
// requesting dropped rows on unsubscribe
if request_flags.contains(UnsubscribeFlags::SendDroppedRows) && !connection.negotiated_v2_binary() {
    // clear the flag or reconnect with the v2 protocol
}

Try / catch

match unsubscribe_result {
    Err(e) if e.to_string().contains("v2 unsubscribe requires binary protocol") => {
        // Version/format mismatch: reconnect with a matched v2-capable server
    }
    other => other,
}

Prevention

When it happens

Trigger: A v2 (binary protocol) client sending Unsubscribe with SendDroppedRows when the subscription actor evaluates queries with the JSON format - a format mismatch between negotiated protocol version and evaluation settings.

Common situations: Client/server version skew; connecting a v2 client to a server that only produces JSON updates; test harnesses forcing the wrong FormatSwitch.

Related errors


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