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
- Align the turso_serverless client version with the server release
- Remove any gateway that rewrites numeric fields in response bodies
- 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
- Keep driver and server versions locked together in deployment
- Ban JSON-transforming gateways on the database path
- Capture and report the raw response — this is a server/intermediary spec violation
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
- invalid value in server response: {e}
- invalid cursor response: {e}
- invalid cursor entry: {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/0b1982df930a8fec.
Report an issue: GitHub.