{"record":{"id":"e623fd15dac8646f","repo":"tursodatabase/turso","slug":"invalid-rowid-in-server-response-e-e623fd","errorCode":null,"errorMessage":"invalid rowid in server response: {e}","messagePattern":"invalid rowid in server response: (.+?)","errorType":"error_code","errorClass":"ProtocolError","httpStatus":null,"severity":"error","filePath":"serverless/python/turso_serverless/connection.py","lineNumber":424,"sourceCode":"        \"\"\"Decode one statement result of a batch response (section 8.4),\n        or None when the statement did not complete.\"\"\"\n        if not isinstance(step_result, dict):\n            return None\n        columns = [c.get(\"name\") or \"\" for c in step_result.get(\"cols\") or []]\n        rows = [tuple(decode_value(v) for v in row) for row in step_result.get(\"rows\") or []]\n        if columns:\n            description = tuple((name, None, None, None, None, None, None) for name in columns)\n            rowcount = -1\n        else:\n            description = None\n            rowcount = step_result.get(\"affected_row_count\") or 0\n        lastrowid = None\n        raw_rowid = step_result.get(\"last_insert_rowid\")\n        if raw_rowid is not None and _is_insert_or_replace(sql):\n            try:\n                lastrowid = int(raw_rowid)\n            except (TypeError, ValueError) as e:\n                raise ProtocolError(f\"invalid rowid in server response: {e}\") from None\n        return BatchResult(\n            rows=rows,\n            description=description,\n            rowcount=rowcount,\n            lastrowid=lastrowid,\n            rows_read=step_result.get(\"rows_read\"),\n            rows_written=step_result.get(\"rows_written\"),\n            query_duration_ms=step_result.get(\"query_duration_ms\"),\n        )\n\n    @staticmethod\n    def _raise_batch_step_error(\n        step_errors: list,\n        statement_count: int,\n        offset: int,\n        commit_index: int | None,\n        results: list,\n    ) -> None:","sourceCodeStart":406,"sourceCodeEnd":442,"githubUrl":"https://github.com/tursodatabase/turso/blob/c1e59287258d99b309e362a63f48822256e2f65f/serverless/python/turso_serverless/connection.py#L406-L442","documentation":"When decoding a single batch statement result, the SDK reads last_insert_rowid for INSERT/REPLACE statements and coerces it to int. If the server sends a rowid that isn't an integer (wrong type or non-numeric string), _decode_batch_statement_result raises ProtocolError. It protects callers from silently returning a bogus lastrowid.","triggerScenarios":"A batch whose INSERT/REPLACE step returns last_insert_rowid as a non-numeric value (string, null-shaped object, float-like string) — e.g. protocol drift, a proxy altering JSON, or a non-conformant server.","commonSituations":"Older/newer server emitting rowids in a different encoding, intermediaries re-serializing numbers as strings with unexpected content, or hitting an incompatible endpoint.","solutions":["Upgrade the SDK and server to aligned versions.","Log the raw step_result for the failing statement (the ValueError in the message shows the bad value) and check what the server actually sent.","If a proxy sits between client and server, bypass it to see whether the response changes.","Only lastrowid for INSERT/REPLACE is parsed; as a workaround, RETURNING the rowid explicitly instead of relying on last_insert_rowid."],"exampleFix":"# before\nres = conn.batch([(\"INSERT INTO t DEFAULT VALUES\", ())])[0]\nrid = res.lastrowid  # ProtocolError if server sent a bad rowid\n# after\nres = conn.batch([(\"INSERT INTO t DEFAULT VALUES RETURNING rowid\", ())])[0]\nrid = res.rows[0][0]  # rowid comes back as a normal column value","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"def is_valid_rowid(raw: object) -> bool:\n    try:\n        int(raw)\n        return True\n    except (TypeError, ValueError):\n        return False","tryCatchPattern":"try:\n    results = conn.batch(stmts)\n    rid = results[0].lastrowid\nexcept ProtocolError as e:\n    if \"invalid rowid\" in str(e):\n        log.error(\"server sent non-integer rowid: %s\", e)\n        rid = None  # fall back to RETURNING-based lookup\n    else:\n        raise","preventionTips":["Prefer RETURNING rowid/id in INSERT statements over last_insert_rowid.","Keep SDK and server versions aligned.","Inspect raw step_result payloads when rowid decoding fails.","Avoid intermediaries that re-serialize numeric JSON fields."],"tags":["python","protocol","server-response","rowid","serverless"],"backgroundTag":"malformed-server-response","analyzedSha":"c1e59287258d99b309e362a63f48822256e2f65f","analyzedAt":"2026-08-31T11:17:35.598Z","contentChangedAt":"2026-08-31T11:17:35.598Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}