unclecode/crawl4ai · warning · HTTPException
Browser with sig={req.sig} not found
Error message
Browser with sig={req.sig} not found What it means
An explicit 404 raised by POST /monitor/actions/kill_browser when the provided signature prefix matches no browser in the hot pool, the cold pool, or the permanent default. Signatures are looked up by prefix match (sig.startswith(req.sig)), so this means the prefix matches nothing - the browser already exited, was killed, or the prefix is wrong.
Source
Thrown at deploy/docker/monitor_routes.py:224
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()
LAST_USED.pop(target_sig, None)
USAGE_COUNT.pop(target_sig, None)View on GitHub (pinned to 7e80152142)
Solutions
- Re-fetch GET /monitor/browsers and use a currently listed signature (first 8 chars).
- Treat 404 as success in idempotent cleanup scripts - the goal (browser gone) is already achieved.
- Longer prefixes are fine and safer: send all 8 characters rather than 2-3 ambiguous ones.
Example fix
# before
resp = post(f"{base}/monitor/actions/kill_browser", json={"sig": sig})
resp.raise_for_status() # 404 on already-reaped browser
# after
resp = post(f"{base}/monitor/actions/kill_browser", json={"sig": sig})
if resp.status_code == 404:
logger.info(f"browser {sig} already gone") # idempotent success
else:
resp.raise_for_status() Defensive patterns
Strategy: fallback
Validate before calling
def browser_exists(base, sig: str) -> bool:
data = get(f"{base}/monitor/browsers").json()
return any(
b.get('sig', '').startswith(sig)
for b in data.get('browsers', [])
) Try / catch
try:
resp = post(f"{base}/monitor/actions/kill_browser", json={'sig': sig})
resp.raise_for_status()
except HTTPError as e:
if e.response.status_code == 404:
pass # already gone - idempotent success
else:
raise Prevention
- Always re-fetch the browser list immediately before killing; sigs go stale between janitor sweeps.
- Send the full 8-char prefix to avoid ambiguous or stale short prefixes.
- Treat 404 as success in cleanup automation.
When it happens
Trigger: Calling kill_browser with a stale sig from an older /monitor/browsers listing (browser already reaped by janitor), a prefix shorter/longer than any current signature, or a typo; also two kills of the same browser where the first succeeded.
Common situations: Dashboard list refreshed slower than the janitor's idle reap interval; operator copies 8-char sig after the pool already recycled it; scripts racing each other to kill the same leaked browser.
Related errors
- Cannot kill permanent browser. Use restart instead.
- Timeout after {timeout}ms waiting for selector '{wait_for}'
- Invalid wait_for parameter: '{wait_for}'. It should be eithe
- Invalid config
- Request failed
AI-assisted analysis of unclecode/crawl4ai@7e80152142 (2026-08-14).
Data as JSON: /api/errors/eea43b87eae2802e.
Report an issue: GitHub.