tursodatabase/turso · error · ProtocolError
invalid cursor entry: {e}
Error message
invalid cursor entry: {e} What it means
ProtocolError raised by _parse_cursor_body() (session.py:71-75) when any NDJSON entry line after the cursor response line fails json.loads — the streamed step/row entries (section 7.2) are corrupt partway through the body. The stream is reset by Session.execute_stmt's ProtocolError handler, so a retry starts a fresh stream.
Source
Thrown at serverless/python/turso_serverless/session.py:75
_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."""
def __init__(self) -> None:
self.result = StmtResult(columns=[], rows=[], affected_rows=0, last_insert_rowid=None)
self.step_error: Optional[ServerError] = None
self.fatal: Optional[ServerError] = None
self.probe_executed = False
self.probe_unreliable = False
self._in_probe = False
def feed(self, entry: dict) -> None:
etype = entry.get("type")View on GitHub (pinned to bad083fafb)
Solutions
- Retry the statement — the driver already reset the stream and the next call opens a new one
- Shrink responses (LIMIT/pagination, select fewer or smaller blob columns) if a proxy size threshold is implicated
- Raise intermediary read/idle timeouts and disable response rewriting on the path
Defensive patterns
Strategy: retry
Try / catch
from turso_serverless.protocol import ProtocolError
for attempt in range(3):
try:
rows = conn.execute(select_sql).fetchall()
break
except ProtocolError as e:
if not str(e).startswith("invalid cursor entry") or attempt == 2:
raise
# truncated NDJSON: stream was reset; page smaller and retry
select_sql = page_query(smaller_limit) Prevention
- Paginate wide/large result sets instead of streaming everything in one cursor batch
- Raise proxy read/idle timeouts for endpoints serving big result sets
- Only auto-retry reads; a truncated response during DML leaves outcome unknown
When it happens
Trigger: A response truncated mid-body (last JSON line cut off) that still decoded as UTF-8; an intermediary appending or mangling lines; server bugs emitting malformed entries for specific value shapes.
Common situations: Proxies with response-size limits truncating large result sets; flaky links dropping tail bytes of chunked responses; rows containing very large blobs hitting an intermediary buffer limit.
Related errors
- invalid value in server response: {e}
- invalid cursor response: {e}
- invalid rowid in server response: {e}
- invalid pipeline response: {e}
- expected get_autocommit result in pipeline response, got {re
AI-assisted analysis of tursodatabase/turso@bad083fafb (2026-08-16).
Data as JSON: /api/errors/6ba3d0f2fca96b47.
Report an issue: GitHub.