unclecode/crawl4ai · warning · HTTPException
Cannot kill permanent browser. Use restart instead.
Error message
Cannot kill permanent browser. Use restart instead.
What it means
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.
Source
Thrown at deploy/docker/monitor_routes.py:221
async with LOCK:
# Check hot pool
for sig in HOT_POOL.keys():
if sig.startswith(req.sig):
target_sig = sig
pool_type = "hot"
break
# Check cold pool
if not target_sig:
for sig in COLD_POOL.keys():
if sig.startswith(req.sig):
target_sig = sig
pool_type = "cold"
break
# Check if trying to kill permanent
if DEFAULT_CONFIG_SIG and DEFAULT_CONFIG_SIG.startswith(req.sig):
raise HTTPException(403, "Cannot kill permanent browser. Use restart instead.")
if not target_sig:
raise HTTPException(404, f"Browser with sig={req.sig} not found")
# Warn if there are active requests (browser might be in use)
monitor = get_monitor()
active_count = len(monitor.get_active_requests())
if active_count > 0:
logger.warning(f"Killing browser {target_sig[:8]} while {active_count} requests are active - may cause failures")
# Kill the browser
if pool_type == "hot":
browser = HOT_POOL.pop(target_sig)
else:
browser = COLD_POOL.pop(target_sig)
with suppress(Exception):
await browser.close()View on GitHub (pinned to 7e80152142)
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.
Example fix
# before
post(f"{base}/monitor/actions/kill_browser", json={"sig": sig}) # 403 if permanent
# after
if sig == 'permanent' or sig == default_sig[:8]:
post(f"{base}/monitor/actions/restart_browser", json={"sig": sig})
else:
post(f"{base}/monitor/actions/kill_browser", json={"sig": sig}) Defensive patterns
Strategy: type-guard
Validate before calling
def is_permanent_sig(sig: str, default_sig: str) -> bool:
return bool(default_sig) and default_sig.startswith(sig)
# route to restart instead of kill when target is permanent
action = 'restart_browser' if is_permanent_sig(sig, default_sig) else 'kill_browser' Type guard
def can_kill(sig: str, default_sig: str | None) -> bool:
"""True when sig does NOT resolve to the permanent default browser."""
return not (default_sig and default_sig.startswith(sig)) Try / catch
try:
resp = post(f"{base}/monitor/actions/kill_browser", json={'sig': sig})
except HTTPError as e:
if e.response.status_code == 403:
resp = post(f"{base}/monitor/actions/restart_browser", json={'sig': sig})
else:
raise Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Browser with sig={req.sig} not found
- Monitor not initialized
- str(e)
- Invalid metric: {metric}. Must be one of: memory, requests,
- Invalid window: {window}. Only '5m' is currently supported
AI-assisted analysis of unclecode/crawl4ai@7e80152142 (2026-08-14).
Data as JSON: /api/errors/94807ea7f62f3ec6.
Report an issue: GitHub.