tursodatabase/turso · error

Failed to decode PullUpdatesRequest: {}

Error message

Failed to decode PullUpdatesRequest: {}

What it means

/pull-updates expects the raw HTTP body to be a prost-encoded PullUpdatesReqProtoBody protobuf message. This error means prost's Message::decode rejected the bytes, so what arrived is not a valid protobuf message of that type; the connection handler turns it into an HTTP 500 with 'Internal Server Error: Failed to decode PullUpdatesRequest: ...'. Typical causes are a non-protobuf body, truncation, or field tag/type drift between client and server proto definitions.

Source

Thrown at cli/sync_server.rs:488

            })
        } else {
            stmt.run_ignore_rows()?;
            Ok(StmtResult {
                cols: vec![],
                rows: vec![],
                affected_row_count: 0,
                last_insert_rowid: None,
                replication_index: None,
                rows_read: 0,
                rows_written: 0,
                query_duration_ms: 0.0,
            })
        }
    }

    fn handle_pull_updates(&self, body: &[u8]) -> Result<HttpResponse> {
        let req = <PullUpdatesReqProtoBody as Message>::decode(body)
            .map_err(|e| anyhow!("Failed to decode PullUpdatesRequest: {}", e))?;

        debug!(
            "Pull updates request: server_revision={}, client_revision={}",
            req.server_revision, req.client_revision
        );

        let encoding =
            PageUpdatesEncodingReq::try_from(req.encoding).unwrap_or(PageUpdatesEncodingReq::Raw);

        if encoding == PageUpdatesEncodingReq::Zstd {
            return Err(anyhow!("Zstd encoding is not supported"));
        }

        if PullUpdatesStreamKind::try_from(req.stream_kind).unwrap_or(PullUpdatesStreamKind::Pages)
            == PullUpdatesStreamKind::MvccLogicalLog
        {
            return self.handle_logical_pull_updates(&req);
        }

View on GitHub (pinned to bad083fafb)

Solutions

  1. Send exactly the bytes produced by PullUpdatesReqProtoBody::encode_to_vec() as the request body.
  2. Verify the HTTP Content-Length matches the encoded body length and that no proxy modifies the payload.
  3. Rebuild client and server from the same workspace commit so the protobuf definitions match.
  4. If every request fails decode, log body length and content-type: a consistent failure means wrong wire format, not corruption.

Example fix

// before: JSON body
let resp = post(url, serde_json::to_vec(&req)?).await?;

// after: raw protobuf body
use prost::Message;
let resp = post(url, PullUpdatesReqProtoBody::encode_to_vec(&req)).await?;
Defensive patterns

Strategy: validation

Validate before calling

use prost::Message;
let body = PullUpdatesReqProtoBody::encode_to_vec(&req);
// round-trip check: if this fails locally, the server will fail too
PullUpdatesReqProtoBody::decode(body.as_slice())
    .map_err(|e| format!("request body is not a decodable PullUpdatesRequest: {e}"))?;

Try / catch

On HTTP 500 containing 'Failed to decode PullUpdatesRequest', do not retry the same bytes: log body length and content-type, re-encode the request from the proto type, and retry once.

Prevention

When it happens

Trigger: POST /pull-updates with a JSON or text body; a body truncated by a wrong Content-Length or a rewriting proxy; a client compiled against a different revision of sync/engine/src/server_proto.rs whose field tags/types disagree with the server's.

Common situations: Testing the endpoint with curl before a real client exists; version skew between client SDK and server after proto schema changes; middleware that re-encodes, compresses, or truncates request bodies.

Understand the failure class

Related errors


AI-assisted analysis of tursodatabase/turso@bad083fafb (2026-08-16). Data as JSON: /api/errors/f361e637be8ea6ef. Report an issue: GitHub.