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] = None

View on GitHub (pinned to bad083fafb)

Solutions

  1. Verify the database URL (scheme, host, and database path) with curl from the same environment
  2. Bypass the proxy/portal that is answering instead of the database
  3. 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

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


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