{"record":{"id":"67cd714e2679feb7","repo":"tursodatabase/turso","slug":"request-to-url-failed-e-reason","errorCode":null,"errorMessage":"request to {url} failed: {e.reason}","messagePattern":"request to (.+?) failed: (.+?)","errorType":"exception","errorClass":"ProtocolError","httpStatus":null,"severity":"error","filePath":"serverless/python/turso_serverless/session.py","lineNumber":211,"sourceCode":"        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]:\n        \"\"\"Execute a pipeline (section 5). When `track_autocommit` is set, a\n        `get_autocommit` request is appended and its answer refreshes the\n        cached transaction state; the returned results cover only the\n        caller's requests.\"\"\"","sourceCodeStart":193,"sourceCodeEnd":229,"githubUrl":"https://github.com/tursodatabase/turso/blob/bad083fafbefdeae9a42ec19bdaaad8918dcf411/serverless/python/turso_serverless/session.py#L193-L229","documentation":"ProtocolError raised by Session._post (session.py:209-211) when urllib raises URLError — the request never produced an HTTP response. The reason is embedded (DNS failure, connection refused, TLS certificate verification failure, timeout). The stream is reset before raising, so any open transaction is gone.","triggerScenarios":"Hostname typo or nonexistent database host; DNS resolution failing inside the runtime; firewall or sandbox blocking outbound egress; TLS errors from self-signed certs or missing CA bundles; proxy env vars (http_proxy/https_proxy) misdirecting urllib.","commonSituations":"Serverless functions (Lambda, Cloud Run jobs) without network egress permissions; local dev behind VPN DNS; containers with an incomplete CA store; corporate proxy variables set in the environment that urllib picks up automatically.","solutions":["Test reachability from the same environment: curl -v <database-url>","Fix the runtime's DNS/egress or proxy env vars","For TLS failures, install a CA bundle (certifi) and point the runtime at it","Retry only transient reasons (timeout, reset); fail fast on DNS or certificate errors"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"import socket, urllib.parse\n\n\ndef endpoint_reachable(url: str, timeout: float = 5.0) -> bool:\n    \"\"\"Cheap pre-flight: resolve + TCP connect before starting work.\"\"\"\n    p = urllib.parse.urlparse(url if \"://\" in url else \"https://\" + url)\n    try:\n        socket.setdefaulttimeout(timeout)\n        socket.create_connection((p.hostname, p.port or 443), timeout=timeout).close()\n        return True\n    except OSError:\n        return False","typeGuard":null,"tryCatchPattern":"import time\nfrom turso_serverless.protocol import ProtocolError\n\n_TRANSIENT = (\"timed out\", \"Connection reset\", \"temporarily unavailable\")\n\ndef run(conn, sql, params=()):\n    for attempt in range(3):\n        try:\n            return conn.execute(sql, params).fetchall()\n        except ProtocolError as e:\n            reason = str(e)\n            if \"request to\" not in reason or not any(t in reason for t in _TRANSIENT):\n                raise  # DNS/cert failures are deterministic: fail fast\n            time.sleep(0.5 * 2 ** attempt)","preventionTips":["Check egress/DNS permissions of the runtime (sandboxed functions often block them)","Audit http_proxy/https_proxy env vars — urllib honors them silently","Install a CA bundle (certifi) in slim containers for TLS verification","Retry only transient reasons (timeout/reset); DNS and cert errors need config fixes"],"tags":["python","network","dns","tls","connection"],"backgroundTag":"network-request-failed","analyzedSha":"bad083fafbefdeae9a42ec19bdaaad8918dcf411","analyzedAt":"2026-08-16T23:12:11.798Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}