{"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/c1e59287258d99b309e362a63f48822256e2f65f/serverless/python/turso_serverless/session.py#L51-L87","documentation":"The first non-blank line of a cursor response body must be a JSON object describing the cursor. _parse_cursor_body() runs json.loads on it and, on failure, raises ProtocolError('invalid cursor response: ...') carrying the parser's message. In practice the body's first line is not protocol JSON at all - it is whatever actually answered the HTTP request.","triggerScenarios":"An HTML or plain-text error page from a reverse proxy, WAF, or auth layer served where the protocol response was expected; a truncated first line; a wrong URL that returns a human-facing 404 page.","commonSituations":"Wrong endpoint URL (hits a website, not the database service); expired auth token producing an error body with a 200 status; corporate proxies injecting block or login pages.","solutions":["Verify the URL points at the Turso/Hrana endpoint and uses the right scheme","Check the auth token and custom headers are valid and being sent","Inspect the first bytes of the raw body to identify what actually answered, then fix that layer"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"def endpoint_is_json(url: str) -> bool:\n    # Cheap preflight: an HTML answer here means wrong URL or intercepting proxy\n    resp = http.get(url, timeout=5)\n    ctype = resp.headers.get(\"content-type\", \"\")\n    return \"json\" in ctype or \"octet-stream\" in ctype","typeGuard":null,"tryCatchPattern":"from turso_serverless.protocol import ProtocolError\n\ntry:\n    result = session.execute_stmt(sql)\nexcept ProtocolError as e:\n    if \"invalid cursor response\" in str(e):\n        raise RuntimeError(\n            \"Endpoint did not return the protocol; check URL, token, and proxies\"\n        ) from e\n    raise","preventionTips":["Verify the endpoint URL and scheme at startup with a minimal query","Check token validity before long-running jobs","Inspect the first bytes of failing bodies to identify HTML block pages early"],"tags":["python","protocol","http","json","proxy"],"backgroundTag":"invalid-json-response","analyzedSha":"c1e59287258d99b309e362a63f48822256e2f65f","analyzedAt":"2026-08-20T07:02:18.389Z","contentChangedAt":"2026-08-20T07:02:18.389Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}