tursodatabase/turso · error · ProtocolError

invalid rowid in server response: {e}

Error message

invalid rowid in server response: {e}

What it means

ProtocolError raised by _CursorDecoder._step_end (session.py:120-129) when a step_end entry carries a last_insert_rowid that int() cannot convert — a non-numeric string, list, or dict. The server violated the section 7.2 entry shape. Because it is raised inside execute_stmt's try block, the stream is reset before the error reaches the caller.

Source

Thrown at serverless/python/turso_serverless/session.py:129

        else:
            self._in_probe = False
            self.result.columns = [c.get("name") or "" for c in entry.get("cols") or []]

    def _row(self, entry: dict) -> None:
        if self._in_probe or self.step_error is not None:
            return
        self.result.rows.append(tuple(decode_value(v) for v in entry.get("row") or []))

    def _step_end(self, entry: dict) -> None:
        if self._in_probe:
            return
        self.result.affected_rows = entry.get("affected_row_count") or 0
        rowid = entry.get("last_insert_rowid")
        if rowid is not None:
            try:
                self.result.last_insert_rowid = int(rowid)
            except (TypeError, ValueError) as e:
                raise ProtocolError(f"invalid rowid in server response: {e}") from None

    def _step_error(self, entry: dict) -> None:
        if entry.get("step") == _PROBE_STEP:
            self.probe_unreliable = True
        elif self.step_error is None:
            self.step_error = _server_error(entry.get("error"))
        self._in_probe = False


class Session:
    """Manages one server-side stream: the baton, the base URL, and the
    server-reported transaction state."""

    def __init__(
        self,
        url: str,
        auth_token: Optional[str] = None,
        remote_encryption_key: Optional[str] = None,

View on GitHub (pinned to bad083fafb)

Solutions

  1. Align the turso_serverless client version with the server release
  2. Remove any gateway that rewrites numeric fields in response bodies
  3. Capture the raw response and report the protocol violation — this cannot be fixed client-side
Defensive patterns

Strategy: try-catch

Try / catch

from turso_serverless.protocol import ProtocolError

try:
    cur = conn.execute("INSERT INTO t VALUES (?)", (x,))
except ProtocolError as e:
    if "invalid rowid" not in str(e):
        raise
    # the statement may have committed: verify before any retry
    got = conn.execute("SELECT 1 FROM t WHERE x = ?", (x,)).fetchone()
    if got is None:
        conn.execute("INSERT INTO t VALUES (?)", (x,))

Prevention

When it happens

Trigger: A server build emits last_insert_rowid in an unexpected format (stringified, nested object); a JSON-transforming intermediary stringifies or wraps numeric fields.

Common situations: Version skew between driver and server; API gateways that 'normalize' numbers to strings; alternative server implementations that are not fully protocol-conformant.

Related errors


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