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

  1. Re-fetch GET /monitor/browsers and use a currently listed signature (first 8 chars).
  2. Treat 404 as success in idempotent cleanup scripts - the goal (browser gone) is already achieved.
  3. 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

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


AI-assisted analysis of unclecode/crawl4ai@7e80152142 (2026-08-14). Data as JSON: /api/errors/eea43b87eae2802e. Report an issue: GitHub.