{"record":{"id":"30536b28528c46a9","repo":"tursodatabase/turso","slug":"http-status-e-code-message","errorCode":null,"errorMessage":"HTTP status {e.code}: {message}","messagePattern":"HTTP status (.+?): (.+?)","errorType":"exception","errorClass":"ProtocolError","httpStatus":null,"severity":"error","filePath":"serverless/python/turso_serverless/session.py","lineNumber":207,"sourceCode":"        req = urllib.request.Request(url, data=data, headers=self._headers(), method=\"POST\")\n        try:\n            with urllib.request.urlopen(req) as resp:\n                return resp.read()\n        except urllib.error.HTTPError as e:\n            raw = e.read().decode(\"utf-8\", errors=\"replace\")\n            self._reset_stream()\n            message = None\n            try:\n                parsed = json.loads(raw)\n                if isinstance(parsed, dict):\n                    for key in (\"error\", \"message\"):\n                        if isinstance(parsed.get(key), str):\n                            message = parsed[key]\n                            break\n            except ValueError:\n                pass\n            if message is not None:\n                raise ProtocolError(f\"HTTP status {e.code}: {message}\") from None\n            raise ProtocolError(f\"HTTP status {e.code}\") from None\n        except urllib.error.URLError as e:\n            self._reset_stream()\n            raise ProtocolError(f\"request to {url} failed: {e.reason}\") from None\n        except (http.client.HTTPException, OSError) as e:\n            # Reading the body can fail after urlopen returned, e.g. with\n            # IncompleteRead on a truncated chunked response or a connection\n            # reset; URLError does not cover these, but they are equally\n            # fatal for the stream.\n            self._reset_stream()\n            raise ProtocolError(f\"request to {url} failed: {e!r}\") from None\n\n    def _update_stream(self, baton: Optional[str], base_url: Optional[str]) -> None:\n        self._baton = baton\n        if base_url:\n            self._base_url = normalize_url(base_url)\n\n    def execute_pipeline(self, requests: list[dict], track_autocommit: bool = True) -> list[dict]:","sourceCodeStart":189,"sourceCodeEnd":225,"githubUrl":"https://github.com/tursodatabase/turso/blob/bad083fafbefdeae9a42ec19bdaaad8918dcf411/serverless/python/turso_serverless/session.py#L189-L225","documentation":"ProtocolError raised by Session._post (session.py:193-208) for any non-2xx HTTP status. The driver reads the error body, tries to parse it as JSON, and embeds its 'error' or 'message' string after the status code. Before raising, it resets the stream (baton cleared, autocommit restored), which also rolls back any open transaction. This is the umbrella surface for auth failures, stale streams, rate limits, and server errors.","triggerScenarios":"401/403 with an invalid or expired auth token; 404 from a mistyped database URL; 4xx when a long-idle connection's baton/stream expired server-side; 429 rate limiting; 5xx server faults. All surface as 'HTTP status <code>: <server message>'.","commonSituations":"Expired Turso API tokens in long-running services; URLs copied without the database path or with the wrong region host; free-tier rate limits under burst load; connections idle for minutes to hours whose next statement hits a stale stream.","solutions":["Branch on the code: 401/403 -> create a new token and reconnect; 404 -> fix the database URL; 429 -> back off (honor Retry-After); 5xx -> retry with jitter","After any of these, keep using the same Connection — the stream was reset and the next statement opens a fresh one — or reconnect if the token itself was the problem","Refresh tokens before expiry rather than after failure","Add a startup probe (SELECT 1) to surface bad URLs and tokens at boot instead of on first user request"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"def probe_connection(url: str, token: str) -> None:\n    \"\"\"Fail at startup, not on the first user request.\"\"\"\n    conn = connect(url, auth_token=token)\n    conn.execute(\"SELECT 1\").fetchall()\n    conn.close()","typeGuard":null,"tryCatchPattern":"import re, time\nfrom turso_serverless.protocol import ProtocolError\n\n_HTTP = re.compile(r\"^HTTP status (\\d{3})\")\n\ndef run(conn_factory, sql, params=()):\n    for attempt in range(5):\n        try:\n            return conn_factory().execute(sql, params).fetchall()\n        except ProtocolError as e:\n            m = _HTTP.match(str(e))\n            if not m:\n                raise\n            code = int(m.group(1))\n            if code in (401, 403):\n                raise RuntimeError(\"auth failed: refresh token\") from e\n            if code == 429 or code >= 500:\n                time.sleep(min(2 ** attempt, 30))\n                continue\n            raise","preventionTips":["Rotate/refresh auth tokens before expiry on a schedule, not on failure","Store the full database URL (host + database path) in validated config","Respect 429 Retry-After with backoff and jitter; never retry 4xx other than 429","Add a boot-time SELECT 1 probe in long-running services"],"tags":["python","http","authentication","rate-limit","protocol"],"backgroundTag":"http-error-status","analyzedSha":"bad083fafbefdeae9a42ec19bdaaad8918dcf411","analyzedAt":"2026-08-16T23:12:11.798Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}