{"record":{"id":"15b4f9f9b83944c2","repo":"tursodatabase/turso","slug":"invalid-cursor-response-e","errorCode":null,"errorMessage":"invalid cursor response: {e}","messagePattern":"invalid cursor response: (.+?)","errorType":"exception","errorClass":"ProtocolError","httpStatus":null,"severity":"error","filePath":"serverless/python/turso_serverless/session.py","lineNumber":69,"sourceCode":"    rows: list[tuple]\n    affected_rows: int\n    last_insert_rowid: Optional[int]\n\n\n# Index of the autocommit probe step appended to every cursor batch.\n_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","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/tursodatabase/turso/blob/bad083fafbefdeae9a42ec19bdaaad8918dcf411/serverless/python/turso_serverless/session.py#L51-L87","documentation":"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.","triggerScenarios":"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.","commonSituations":"URL pointing at a web app or portal rather than the database endpoint; corporate proxies injecting notice pages; captive portals on restricted networks.","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"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"from turso_serverless.protocol import ProtocolError\n\ntry:\n    conn.execute(sql)\nexcept ProtocolError as e:\n    if not str(e).startswith(\"invalid cursor response\"):\n        raise\n    # body was not JSON (often a proxy error page): verify endpoint, then retry fresh\n    conn = connect(URL, auth_token=TOKEN)\n    conn.execute(sql)","preventionTips":["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"],"tags":["python","protocol","cursor","json","response-parsing"],"backgroundTag":"malformed-server-response","analyzedSha":"bad083fafbefdeae9a42ec19bdaaad8918dcf411","analyzedAt":"2026-08-16T23:12:11.798Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}