tursodatabase/turso · error · ProtocolError
invalid cursor response: {e}
Error message
invalid cursor response: {e} What it means
ProtocolError raised by _parse_cursor_body() (session.py:66-69) when the first non-empty line of the /v3/cursor body is not valid JSON — the cursor response object (section 7.1) is unreadable. The json error detail is embedded. Like other ProtocolErrors from statement execution, it causes a stream reset, after which the next statement starts a new stream.
Source
Thrown at serverless/python/turso_serverless/session.py:69
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."""
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] = NoneView on GitHub (pinned to bad083fafb)
Solutions
- Verify the database URL (scheme, host, and database path) with curl from the same environment
- Bypass the proxy/portal that is answering instead of the database
- Retry after fixing the path — the stream was already reset by the driver
Defensive patterns
Strategy: retry
Try / catch
from turso_serverless.protocol import ProtocolError
try:
conn.execute(sql)
except ProtocolError as e:
if not str(e).startswith("invalid cursor response"):
raise
# body was not JSON (often a proxy error page): verify endpoint, then retry fresh
conn = connect(URL, auth_token=TOKEN)
conn.execute(sql) Prevention
- Verify the URL with curl from the deployment environment before shipping
- Ensure no proxy serves HTML block/notice pages on the database path
- Treat 'first line is not JSON' as a routing problem first, a server bug second
When it happens
Trigger: The body's first line is an HTML error page (proxy block page served with 200), plain-text diagnostics, or garbage bytes instead of the JSON object.
Common situations: URL pointing at a web app or portal rather than the database endpoint; corporate proxies injecting notice pages; captive portals on restricted networks.
Related errors
- invalid pipeline response: {e}
- invalid value in server response: {e}
- cursor response body ended before the cursor response line
- invalid cursor entry: {e}
- invalid rowid in server response: {e}
AI-assisted analysis of tursodatabase/turso@bad083fafb (2026-08-16).
Data as JSON: /api/errors/15b4f9f9b83944c2.
Report an issue: GitHub.