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
- Retry idempotent reads — the stream was reset, the next statement opens a fresh one
- Reduce response size with pagination (LIMIT/OFFSET or keyset) and avoid selecting huge blobs unneeded
- 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
- Stream large reads in pages (LIMIT/keyset) instead of one giant cursor batch
- Avoid selecting huge blob columns you do not need
- Tune proxy/LB read timeouts above your worst-case query duration
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
- request to {url} failed: {e.reason}
- getAllRows: exceeded ${MAX_IO_RETRIES} IO retries
- HTTP request failed: ${e instanceof Error ? e.message : Stri
- Query timed out
- HTTP error! status: ${response.status}
AI-assisted analysis of tursodatabase/turso@bad083fafb (2026-08-16).
Data as JSON: /api/errors/ab6809b2f235b294.
Report an issue: GitHub.