{"repo":"NousResearch/hermes-agent","public_id":"2746","display_label":"Error handling for browser provider network calls","summary":"A bug report and corresponding fixes addressing unprotected network calls in browser providers that crash when a connection failure occurs. The fix wraps these calls with error handling to prevent crashes.","subsystem":"agent","counts":{"members":3,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":1,"merged_prs":1,"attempt_prs":1},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":26247,"merged_at":"2026-05-15T08:53:06+00:00"},"resolution":{"state":"resolved","by":"pr-26247"},"freshness":{"state":"verified","last_verified_at":"2026-05-15T08:53:06+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.6666666666666666},"fix_edges":[{"issue_number":2746,"pr_number":26247,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-05-15T08:53:06+00:00"}],"members":[{"kind":"issue","number":2746,"title":"[Bug]: Unprotected network calls in browser providers crash on connection failure","url":null,"state":"closed"},{"kind":"pr","number":2753,"title":"fix(tools): wrap browser provider network calls with error handling","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#26247","number":26247}],"category":"uncategorized","implication":"needs_llm"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":26247,"title":"fix(tools): wrap browser provider network calls with error handling","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["tools/browser_providers/browser_use.py","tools/browser_providers/browserbase.py","tools/browser_providers/firecrawl.py"],"fix_hunks":[{"file":"tools/browser_providers/browser_use.py","hunks":["@@ -137,12 +137,22 @@ def create_session(self, task_id: str) -> Dict[str, object]:\n             else {}\n         )\n \n-        response = requests.post(\n-            f\"{config['base_url']}/browsers\",\n-            headers=headers,\n-            json=payload,\n-            timeout=30,\n-        )\n+        try:\n+            response = requests.post(\n+                f\"{config['base_url']}/browsers\",\n+                headers=headers,\n+                json=payload,\n+                timeout=30,\n+            )\n+        except requests.RequestException as exc:\n+            # Managed mode: propagate raw so callers can retry with the\n+            # preserved idempotency key. Direct mode: wrap network failures\n+            # into a clean RuntimeError for end users.\n+            if managed_mode:\n+                raise\n+            raise RuntimeError(\n+                f\"Browser Use API connection failed: {exc}\"\n+            ) from exc\n \n         if not response.ok:\n             if managed_mode and not _should_preserve_pending_create_key(response):"]},{"file":"tools/browser_providers/browserbase.py","hunks":["@@ -92,45 +92,50 @@ def create_session(self, task_id: str) -> Dict[str, object]:\n             \"X-BB-API-Key\": [redacted:secret-assignment]\"api_key\"],\n         }\n \n-        response = requests.post(\n-            f\"{config['base_url']}/v1/sessions\",\n-            headers=headers,\n-            json=session_config,\n-            timeout=30,\n-        )\n-\n-        proxies_fallback = False\n-        keepalive_fallback = False\n-\n-        # Handle 402 — paid features unavailable\n-        if response.status_code == 402:\n-            if enable_keep_alive:\n-                keepalive_fallback = True\n-                logger.warning(\n-                    \"keepAlive may require paid plan (402), retrying without it. \"\n-                    \"Sessions may timeout during long operations.\"\n-                )\n-                session_config.pop(\"keepAlive\", None)\n-                response = requests.post(\n-                    f\"{config['base_url']}/v1/sessions\",\n-                    headers=headers,\n-                    json=session_config,\n-                    timeout=30,\n-                )\n+        try:\n+            response = requests.post(\n+                f\"{config['base_url']}/v1/sessions\",\n+                headers=headers,\n+                json=session_config,\n+                timeout=30,\n+            )\n \n-            if response.status_code == 402 and enable_proxies:\n-                proxies_fallback = True\n-                logger.warning(\n-                    \"Proxies unavailable (402), retrying without proxies. \"\n-                    \"Bot detection may be less effective.\"\n-                )\n-                session_config.pop(\"proxies\", None)\n-                response = requests.post(\n-                    f\"{config['base_url']}/v1/sessions\",\n-                    headers=headers,\n-                    json=session_config,\n-                    timeout=30,\n-                )\n+            proxies_fallback = False\n+            keepalive_fallback = False\n+\n+            # Handle 402 — paid features unavailable\n+            if response.status_code == 402:\n+                if enable_keep_alive:\n+                    keepalive_fallback = True\n+                    logger.warning(\n+                        \"keepAlive may require paid plan (402), retrying without it. \"\n+                        \"Sessions may timeout during long operations.\"\n+                    )\n+                    session_config.pop(\"keepAlive\", None)\n+                    response = requests.post(\n+                        f\"{config['base_url']}/v1/sessions\",\n+                        headers=headers,\n+                        json=session_config,\n+                        timeout=30,\n+                    )\n+\n+                if response.status_code == 402 and enable_proxies:\n+                    proxies_fallback = True\n+                    logger.warning(\n+                        \"Proxies unavailable (402), retrying without proxies. \"\n+                        \"Bot detection may be less effective.\"\n+                    )\n+                    session_config.pop(\"proxies\", None)\n+                    response = requests.post(\n+                        f\"{config['base_url']}/v1/sessions\",\n+                        headers=headers,\n+                        json=session_config,\n+                        timeout=30,\n+                    )\n+        except requests.RequestException as exc:\n+            raise RuntimeError(\n+                f\"Browserbase API connection failed: {exc}\"\n+            ) from exc\n \n         if not response.ok:\n             raise RuntimeError("]},{"file":"tools/browser_providers/firecrawl.py","hunks":["@@ -47,12 +47,17 @@ def create_session(self, task_id: str) -> Dict[str, object]:\n \n         body: Dict[str, object] = {\"ttl\": ttl}\n \n-        response = requests.post(\n-            f\"{self._api_url()}/v2/browser\",\n-            headers=self._headers(),\n-            json=body,\n-            timeout=30,\n-        )\n+        try:\n+            response = requests.post(\n+                f\"{self._api_url()}/v2/browser\",\n+                headers=self._headers(),\n+                json=body,\n+                timeout=30,\n+            )\n+        except requests.RequestException as exc:\n+            raise RuntimeError(\n+                f\"Firecrawl API connection failed: {exc}\"\n+            ) from exc\n \n         if not response.ok:\n             raise RuntimeError(\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}