{"record":{"id":"6ba3d0f2fca96b47","repo":"tursodatabase/turso","slug":"invalid-cursor-entry-e","errorCode":null,"errorMessage":"invalid cursor entry: {e}","messagePattern":"invalid cursor entry: (.+?)","errorType":"exception","errorClass":"ProtocolError","httpStatus":null,"severity":"error","filePath":"serverless/python/turso_serverless/session.py","lineNumber":75,"sourceCode":"_PROBE_STEP = 1\n\n\ndef _parse_cursor_body(raw: bytes) -> tuple[dict, list[dict]]:\n    \"\"\"Split a cursor response body (section 7.1) into the cursor response\n    line and the decoded entries that follow it.\"\"\"\n    lines = [line for line in raw.decode(\"utf-8\").splitlines() if line.strip()]\n    if not lines:\n        raise ProtocolError(\"cursor response body ended before the cursor response line\")\n    try:\n        cursor_resp = json.loads(lines[0])\n    except ValueError as e:\n        raise ProtocolError(f\"invalid cursor response: {e}\") from None\n    entries = []\n    for line in lines[1:]:\n        try:\n            entries.append(json.loads(line))\n        except ValueError as e:\n            raise ProtocolError(f\"invalid cursor entry: {e}\") from None\n    return cursor_resp, entries\n\n\nclass _CursorDecoder:\n    \"\"\"Folds streamed cursor entries (section 7.2) into a statement result,\n    tracking the trailing autocommit probe step separately from the caller's\n    step.\"\"\"\n\n    def __init__(self) -> None:\n        self.result = StmtResult(columns=[], rows=[], affected_rows=0, last_insert_rowid=None)\n        self.step_error: Optional[ServerError] = None\n        self.fatal: Optional[ServerError] = None\n        self.probe_executed = False\n        self.probe_unreliable = False\n        self._in_probe = False\n\n    def feed(self, entry: dict) -> None:\n        etype = entry.get(\"type\")","sourceCodeStart":57,"sourceCodeEnd":93,"githubUrl":"https://github.com/tursodatabase/turso/blob/bad083fafbefdeae9a42ec19bdaaad8918dcf411/serverless/python/turso_serverless/session.py#L57-L93","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"from turso_serverless.protocol import ProtocolError\n\nfor attempt in range(3):\n    try:\n        rows = conn.execute(select_sql).fetchall()\n        break\n    except ProtocolError as e:\n        if not str(e).startswith(\"invalid cursor entry\") or attempt == 2:\n            raise\n        # truncated NDJSON: stream was reset; page smaller and retry\n        select_sql = page_query(smaller_limit)","preventionTips":["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"],"tags":["python","protocol","ndjson","truncation","response-parsing"],"backgroundTag":"malformed-server-response","analyzedSha":"bad083fafbefdeae9a42ec19bdaaad8918dcf411","analyzedAt":"2026-08-16T23:12:11.798Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}