{"record":{"id":"94807ea7f62f3ec6","repo":"unclecode/crawl4ai","slug":"cannot-kill-permanent-browser-use-restart-instead","errorCode":null,"errorMessage":"Cannot kill permanent browser. Use restart instead.","messagePattern":"Cannot kill permanent browser\\. Use restart instead\\.","errorType":"http","errorClass":"HTTPException","httpStatus":403,"severity":"warning","filePath":"deploy/docker/monitor_routes.py","lineNumber":221,"sourceCode":"        async with LOCK:\n            # Check hot pool\n            for sig in HOT_POOL.keys():\n                if sig.startswith(req.sig):\n                    target_sig = sig\n                    pool_type = \"hot\"\n                    break\n\n            # Check cold pool\n            if not target_sig:\n                for sig in COLD_POOL.keys():\n                    if sig.startswith(req.sig):\n                        target_sig = sig\n                        pool_type = \"cold\"\n                        break\n\n            # Check if trying to kill permanent\n            if DEFAULT_CONFIG_SIG and DEFAULT_CONFIG_SIG.startswith(req.sig):\n                raise HTTPException(403, \"Cannot kill permanent browser. Use restart instead.\")\n\n            if not target_sig:\n                raise HTTPException(404, f\"Browser with sig={req.sig} not found\")\n\n            # Warn if there are active requests (browser might be in use)\n            monitor = get_monitor()\n            active_count = len(monitor.get_active_requests())\n            if active_count > 0:\n                logger.warning(f\"Killing browser {target_sig[:8]} while {active_count} requests are active - may cause failures\")\n\n            # Kill the browser\n            if pool_type == \"hot\":\n                browser = HOT_POOL.pop(target_sig)\n            else:\n                browser = COLD_POOL.pop(target_sig)\n\n            with suppress(Exception):\n                await browser.close()","sourceCodeStart":203,"sourceCodeEnd":239,"githubUrl":"https://github.com/unclecode/crawl4ai/blob/7e801521428ee12509994d39151006f64055ebe3/deploy/docker/monitor_routes.py#L203-L239","documentation":"An explicit 403 raised by POST /monitor/actions/kill_browser when the requested signature prefix matches DEFAULT_CONFIG_SIG - the permanent browser. The permanent browser is the always-on instance serving default-config requests, so killing it would degrade every default crawl; the API intentionally refuses and directs you to restart_browser instead.","triggerScenarios":"Calling POST /monitor/actions/kill_browser with sig equal to (or a prefix of) the default config signature - e.g. sig='permanent' if that prefix matches, or the first 8 chars of DEFAULT_CONFIG_SIG obtained from /monitor/browsers.","commonSituations":"Operator sees a bloated/leaky browser in the dashboard, copies its sig, and does not notice it is the permanent one; automation written to 'recycle any browser over N MB' hits the permanent instance; attempts to free memory by killing the largest browser, which is usually permanent.","solutions":["Use POST /monitor/actions/restart_browser instead - it explicitly supports the permanent browser (sig or 'permanent') via kill + recreate.","Confirm the target's pool type from GET /monitor/browsers before killing; permanent is neither hot nor cold.","Adjust automation to exclude the default signature when selecting kill candidates."],"exampleFix":"# before\npost(f\"{base}/monitor/actions/kill_browser\", json={\"sig\": sig})  # 403 if permanent\n\n# after\nif sig == 'permanent' or sig == default_sig[:8]:\n    post(f\"{base}/monitor/actions/restart_browser\", json={\"sig\": sig})\nelse:\n    post(f\"{base}/monitor/actions/kill_browser\", json={\"sig\": sig})","handlingStrategy":"type-guard","validationCode":"def is_permanent_sig(sig: str, default_sig: str) -> bool:\n    return bool(default_sig) and default_sig.startswith(sig)\n\n# route to restart instead of kill when target is permanent\naction = 'restart_browser' if is_permanent_sig(sig, default_sig) else 'kill_browser'","typeGuard":"def can_kill(sig: str, default_sig: str | None) -> bool:\n    \"\"\"True when sig does NOT resolve to the permanent default browser.\"\"\"\n    return not (default_sig and default_sig.startswith(sig))","tryCatchPattern":"try:\n    resp = post(f\"{base}/monitor/actions/kill_browser\", json={'sig': sig})\nexcept HTTPError as e:\n    if e.response.status_code == 403:\n        resp = post(f\"{base}/monitor/actions/restart_browser\", json={'sig': sig})\n    else:\n        raise","preventionTips":["Check the target's pool type via GET /monitor/browsers before killing.","Automation selecting kill candidates must exclude the default/permanent signature.","Remember restart_browser handles permanent; kill_browser never will by design."],"tags":["fastapi","browser-pool","permissions","admin-actions"],"backgroundTag":null,"analyzedSha":"7e801521428ee12509994d39151006f64055ebe3","analyzedAt":"2026-08-14T20:46:20.673Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}