NanmiCoder/MediaCrawler · error · RuntimeError

Browser failed to start within {config.BROWSER_LAUNCH_TIMEOU

Error message

Browser failed to start within {config.BROWSER_LAUNCH_TIMEOUT} seconds

What it means

Raised by CDPBrowserManager after it spawns the browser process and wait_for_browser_ready() reports the DevTools endpoint still not answering within config.BROWSER_LAUNCH_TIMEOUT seconds. The process was created but never became CDP-reachable — slow start, crash on startup, or a blocked port.

Source

Thrown at tools/cdp_browser.py:277

                "browser_data",
                f"cdp_{config.USER_DATA_DIR % config.PLATFORM}",
            )
            os.makedirs(user_data_dir, exist_ok=True)
            utils.logger.info(f"[CDPBrowserManager] User data directory: {user_data_dir}")

        # Launch browser
        self.launcher.browser_process = self.launcher.launch_browser(
            browser_path=browser_path,
            debug_port=self.debug_port,
            headless=headless,
            user_data_dir=user_data_dir,
        )

        # Wait for browser to be ready
        if not self.launcher.wait_for_browser_ready(
            self.debug_port, config.BROWSER_LAUNCH_TIMEOUT
        ):
            raise RuntimeError(f"Browser failed to start within {config.BROWSER_LAUNCH_TIMEOUT} seconds")

        # Extra wait for CDP service to fully start
        await asyncio.sleep(1)

        # Test CDP connection
        if not await self._test_cdp_connection(self.debug_port):
            utils.logger.warning(
                "[CDPBrowserManager] CDP connection test failed, but will continue to try connecting"
            )

    async def _get_browser_websocket_url(self, debug_port: int) -> str:
        """
        Get browser WebSocket connection URL
        """
        try:
            async with httpx.AsyncClient() as client:
                response = await client.get(
                    f"http://localhost:{debug_port}/json/version", timeout=10

View on GitHub (pinned to d6f7c5bb90)

Solutions

  1. Raise BROWSER_LAUNCH_TIMEOUT (e.g. 60s) in config for slow machines/CI
  2. Kill stale processes and clear the profile lock: pkill -f remote-debugging-port and remove SingletonLock in the user-data-dir
  3. In Docker/CI run the browser with --no-sandbox (or a seccomp profile that allows Chrome's sandbox syscalls)
  4. Free the intended debug port or use another one so readiness polling targets the browser that actually started
  5. Check the browser process stderr — if it exits at once, fix the reported launch error instead of only raising the timeout

Example fix

# before
BROWSER_LAUNCH_TIMEOUT = 10

# after
BROWSER_LAUNCH_TIMEOUT = 60
# plus, for Docker:
# launch_browser args include "--no-sandbox"
Defensive patterns

Strategy: retry

Validate before calling

# cheap readiness probe before relying on the manager
import urllib.request

def browser_ready(port: int, timeout: float = 2.0) -> bool:
    try:
        with urllib.request.urlopen(f"http://127.0.0.1:{port}/json/version", timeout=timeout) as r:
            return r.status == 200
    except Exception:
        return False

Try / catch

for attempt in range(3):
    try:
        ctx = await mgr.init(...)
        break
    except RuntimeError as e:
        if "failed to start" not in str(e) or attempt == 2:
            raise
        utils.logger.warning(f"browser slow to start, attempt {attempt+1}")
        # clean stale profile locks / raise timeout before retry
        subprocess.run(["pkill", "-f", "remote-debugging-port"], check=False)

Prevention

When it happens

Trigger: BROWSER_LAUNCH_TIMEOUT too low for the machine (cold start on slow disks/CI); the browser process immediately exits due to a locked/corrupted user-data-dir from a previous crash; sandbox errors in containers (--no-sandbox missing); the debug port occupied by a stale process so the new browser picks another port and readiness polling checks the wrong one.

Common situations: CI and Docker where Chrome needs --no-sandbox; leftover chrome zombie processes holding the profile lock and port; first launch on a fresh profile doing heavy migration; low-RAM hosts where Chrome starves during startup.

Understand the failure class

Related errors


AI-assisted analysis of NanmiCoder/MediaCrawler@d6f7c5bb90 (2026-08-15). Data as JSON: /api/errors/79c748a54b931645. Report an issue: GitHub.