tursodatabase/turso · error · ProtocolError

request to {url} failed: {e!r}

Error message

request to {url} failed: {e!r}

What it means

ProtocolError raised by Session._post (session.py:212-218) when the response body could not be read after urlopen returned — http.client.HTTPException (e.g. IncompleteRead on a truncated chunked response) or OSError (connection reset). The code comment notes URLError does not cover these, but they are equally fatal for the stream, which is reset. Unlike error 292, the request connected and headers arrived; the body died mid-transfer.

Source

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

                    for key in ("error", "message"):
                        if isinstance(parsed.get(key), str):
                            message = parsed[key]
                            break
            except ValueError:
                pass
            if message is not None:
                raise ProtocolError(f"HTTP status {e.code}: {message}") from None
            raise ProtocolError(f"HTTP status {e.code}") from None
        except urllib.error.URLError as e:
            self._reset_stream()
            raise ProtocolError(f"request to {url} failed: {e.reason}") from None
        except (http.client.HTTPException, OSError) as e:
            # Reading the body can fail after urlopen returned, e.g. with
            # IncompleteRead on a truncated chunked response or a connection
            # reset; URLError does not cover these, but they are equally
            # fatal for the stream.
            self._reset_stream()
            raise ProtocolError(f"request to {url} failed: {e!r}") from None

    def _update_stream(self, baton: Optional[str], base_url: Optional[str]) -> None:
        self._baton = baton
        if base_url:
            self._base_url = normalize_url(base_url)

    def execute_pipeline(self, requests: list[dict], track_autocommit: bool = True) -> list[dict]:
        """Execute a pipeline (section 5). When `track_autocommit` is set, a
        `get_autocommit` request is appended and its answer refreshes the
        cached transaction state; the returned results cover only the
        caller's requests."""
        reqs = list(requests)
        if track_autocommit:
            reqs.append({"type": "get_autocommit"})
        raw = self._post("/v3/pipeline", {"baton": self._baton, "requests": reqs})
        try:
            resp: dict[str, Any] = json.loads(raw)
        except ValueError as e:

View on GitHub (pinned to bad083fafb)

Solutions

  1. Retry idempotent reads — the stream was reset, the next statement opens a fresh one
  2. Reduce response size with pagination (LIMIT/OFFSET or keyset) and avoid selecting huge blobs unneeded
  3. Raise read/idle timeouts on any proxy or load balancer between client and database
Defensive patterns

Strategy: retry

Try / catch

import time
from turso_serverless.protocol import ProtocolError

def fetch_paged(conn, base_sql, page=1000):
    offset, rows = 0, []
    while True:
        try:
            batch = conn.execute(f"{base_sql} LIMIT {page} OFFSET {offset}").fetchall()
        except ProtocolError as e:
            if "IncompleteRead" not in str(e) and "HTTPException" not in str(e):
                raise
            page = max(page // 2, 50)  # truncated mid-body: shrink and retry
            continue
        rows.extend(batch)
        if len(batch) < page:
            return rows
        offset += page

Prevention

When it happens

Trigger: Server or intermediary closes the connection mid-body on a large streamed result set; proxy idle/read timeout firing during a long /v3/cursor response; flaky networks resetting connections under load.

Common situations: Large SELECTs (wide rows, big blobs) exceeding proxy buffer or time budgets; aggressive load-balancer timeouts; mobile or unstable uplinks; serverless function execution limits killing in-flight responses.

Related errors


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