tursodatabase/turso · error · ProtocolError
cursor response body ended before the cursor response line
Error message
cursor response body ended before the cursor response line
What it means
ProtocolError raised by _parse_cursor_body() (session.py:60-65) when the /v3/cursor response body contains no non-empty lines — the stream ended before the required cursor response line (protocol section 7.1). Session.execute_stmt catches it, resets the stream (baton cleared, autocommit restored), and re-raises, so the connection itself survives; the next statement opens a fresh stream.
Source
Thrown at serverless/python/turso_serverless/session.py:65
class StmtResult:
"""The decoded output of one executed statement."""
columns: list[str]
rows: list[tuple]
affected_rows: int
last_insert_rowid: Optional[int]
# Index of the autocommit probe step appended to every cursor batch.
_PROBE_STEP = 1
def _parse_cursor_body(raw: bytes) -> tuple[dict, list[dict]]:
"""Split a cursor response body (section 7.1) into the cursor response
line and the decoded entries that follow it."""
lines = [line for line in raw.decode("utf-8").splitlines() if line.strip()]
if not lines:
raise ProtocolError("cursor response body ended before the cursor response line")
try:
cursor_resp = json.loads(lines[0])
except ValueError as e:
raise ProtocolError(f"invalid cursor response: {e}") from None
entries = []
for line in lines[1:]:
try:
entries.append(json.loads(line))
except ValueError as e:
raise ProtocolError(f"invalid cursor entry: {e}") from None
return cursor_resp, entries
class _CursorDecoder:
"""Folds streamed cursor entries (section 7.2) into a statement result,
tracking the trailing autocommit probe step separately from the caller's
step."""
View on GitHub (pinned to bad083fafb)
Solutions
- Point the client directly at the database HTTP endpoint and retry — the driver reset the stream, so the next statement starts clean
- Fix or remove the proxy that produced the empty body
- If it reproduces against the real endpoint, upgrade client/server and report with the request body that triggered it
Defensive patterns
Strategy: retry
Try / catch
from turso_serverless.protocol import ProtocolError
for attempt in range(2):
try:
rows = conn.execute(sql).fetchall()
break
except ProtocolError as e:
if "ended before the cursor response line" not in str(e) or attempt:
raise
# driver reset the stream; next statement starts a new one Prevention
- Health-check the real endpoint (SELECT 1) in CI so empty-body proxies are caught before deploy
- Do not put rewriting proxies or caches in front of the database URL
- Keep one retry-with-fresh-stream wrapper for transient response-shape failures
When it happens
Trigger: The server returns HTTP 200 with an empty or whitespace-only body for a cursor batch; an intermediary (LB, API gateway) substitutes an empty body; a server bug drops the body while keeping the status.
Common situations: Misconfigured reverse proxy or load balancer in front of the database endpoint; serverless platforms stripping streaming responses; transient server faults during deploys.
Related errors
- invalid cursor response: {e}
- Cannot operate on a closed cursor
- Cannot operate on a closed cursor
- Cannot operate on a closed cursor
- infinite float values cannot be sent over the protocol
AI-assisted analysis of tursodatabase/turso@bad083fafb (2026-08-16).
Data as JSON: /api/errors/a97be9527a76f243.
Report an issue: GitHub.