tursodatabase/turso · error · ProtocolError

expected batch result in pipeline response, got {response}

Error message

expected batch result in pipeline response, got {response}

What it means

Session.execute_batch sends a batch step through the pipeline and expects the reply entry to be type="batch" with a dict-shaped result. If the response entry has any other type or a missing/non-dict result, execute_batch raises ProtocolError — the server answered, but not with a batch result this client can decode.

Source

Thrown at serverless/python/turso_serverless/session.py:330

            self._refresh_autocommit()
        else:
            self._autocommit = decoder.probe_executed
        if decoder.step_error is not None:
            raise decoder.step_error
        return decoder.result

    def execute_batch(self, steps: list[dict]) -> dict:
        """Execute a batch request on the pipeline endpoint (section 6.2)
        and return its raw result: `step_results` and `step_errors` arrays
        with one entry per step. Step failures are reported in those
        arrays, not raised here."""
        results = self.execute_pipeline([{"type": "batch", "batch": {"steps": steps}}])
        result = results[0]
        if result.get("type") == "error":
            raise _server_error(result.get("error"))
        response = result.get("response") or {}
        if response.get("type") != "batch" or not isinstance(response.get("result"), dict):
            raise ProtocolError(f"expected batch result in pipeline response, got {response}")
        return response["result"]

    def close(self) -> None:
        """Close the stream (section 6.8). Errors are ignored: the stream
        may already have expired."""
        if self._baton is not None:
            try:
                self.execute_pipeline([{"type": "close"}], track_autocommit=False)
            except Exception:
                pass
        self._reset_stream()

View on GitHub (pinned to c1e5928725)

Solutions

  1. Align SDK and server versions; the batch response shape (section 6.2/8.4) changed across protocol revisions.
  2. Log the full `response` object embedded in the message to see what the server actually returned.
  3. Bypass proxies/gateways to rule out response rewriting.
  4. Retry the request; if persistent, capture the payload and report to Turso support.

Example fix

# before
result = session.execute_batch(steps)  # ProtocolError: got {'type': 'ok'}
# after
try:
    result = session.execute_batch(steps)
except ProtocolError as e:
    logger.error("unexpected pipeline reply: %s", e)
    raise RuntimeError("server protocol mismatch — upgrade turso_serverless and server together") from e
Defensive patterns

Strategy: try-catch

Validate before calling

def batch_reply_well_formed(entry: dict) -> bool:
    resp = entry.get("response") or {}
    return entry.get("type") != "error" and resp.get("type") == "batch" and isinstance(resp.get("result"), dict)

Try / catch

try:
    result = session.execute_batch(steps)
except ProtocolError as e:
    log.error("unexpected pipeline reply: %s", e)
    raise RuntimeError("client/server pipeline protocol mismatch") from e

Prevention

When it happens

Trigger: Calling execute_batch against a server whose pipeline reply for the batch step is an unexpected shape (empty response object, different type tag), after server upgrades/downgrades, or when the response was altered in transit.

Common situations: Client/server protocol version mismatch, load balancers or proxies rewriting responses, and pointing the SDK at an endpoint that speaks a different pipeline revision.

Related errors


AI-assisted analysis of tursodatabase/turso@c1e5928725 (2026-08-31). Data as JSON: /api/errors/41cd08bbebe66f16. Report an issue: GitHub.