unclecode/crawl4ai · critical · Exception

Failed to start browser: {e}

Error message

Failed to start browser: {e}

What it means

Raised by the managed/native browser launcher when the subprocess launch, the initial startup check, or the fixed-delay readiness waits raise or time out. Before raising it calls await self.cleanup() (killing the process and removing the temp profile dir), then wraps the original failure in a generic Exception prefixed with 'Failed to start browser:'.

Source

Thrown at crawl4ai/browser_manager.py:277:7112

                    stderr=subprocess.PIPE,
                    creationflags=subprocess.DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP
                )
            else:
                self.browser_process = subprocess.Popen(
                    args, 
                    stdout=subprocess.PIPE, 
                    stderr=subprocess.PIPE,
                    preexec_fn=os.setpgrp  # Start in a new process group
                )
                
            # We'll monitor for a short time to make sure it starts properly, but won't keep monitoring
            await asyncio.sleep(0.5)  # Give browser time to start
            await self._initial_startup_check()
            await asyncio.sleep(2)  # Give browser time to start
            return f"http://{self.host}:{self.debugging_port}"
        except Exception as e:
            await self.cleanup()
            raise Exception(f"Failed to start browser: {e}")

    async def _initial_startup_check(self):
        """
        Perform a quick check to make sure the browser started successfully.
        This only runs once at startup rather than continuously monitoring.
        """
        if not self.browser_process:
            return
            
        # Check that process started without immediate termination
        await asyncio.sleep(0.5)
        if self.browser_process.poll() is not None:
            # Process already terminated
            stdout, stderr = b"", b""
            try:
                stdout, stderr = self.browser_process.communicate(timeout=0.5)
            except subprocess.TimeoutExpired:
                pass

View on GitHub (pinned to 7e80152142)

Solutions

  1. Read the text after 'Failed to start browser:' to identify the root cause (FileNotFoundError means the binary path is wrong; 'address already in use' means the port is taken)
  2. Verify the browser binary exists and runs: chromium --version; fix browser_type/binary path in the config
  3. Kill stale browser processes or set a fresh debugging_port / user_data_dir
  4. In Docker/CI, ensure required shared libraries are installed and pass --no-sandbox if the environment lacks a sandbox
  5. Retry once at the call site — cleanup() has already run, so a retry starts from a clean state

Example fix

# before
url = await browser.start()  # raises: Failed to start browser: ...
# after
from pathlib import Path
if not Path(browser_config.binary or shutil.which("chromium")).exists():
    raise RuntimeError("browser binary missing — install chromium first")
try:
    url = await browser.start()
except Exception as e:
    logger.error("browser start failed: %s", e)
    raise
Defensive patterns

Strategy: retry

Validate before calling

import shutil, socket

def browser_launchable(binary: str | None, port: int) -> list[str]:
    problems = []
    if not (binary and __import__('pathlib').Path(binary).exists()) and not shutil.which(binary or "chromium"):
        problems.append("browser binary not found")
    with socket.socket() as s:
        try:
            s.bind(("127.0.0.1", port))
        except OSError:
            problems.append(f"debugging port {port} already in use")
    return problems

Try / catch

try:
    ws_url = await browser.start()
except Exception as e:
    logger.error("browser failed to start (cleanup already ran): %s", e)
    if "not found" in str(e) or "address already in use" in str(e):
        raise  # fix environment, retrying will not help
    await asyncio.sleep(2)
    ws_url = await browser.start()  # state is clean after internal cleanup()

Prevention

When it happens

Trigger: Managed/Native browser mode where the Chrome/Firefox binary is missing or not executable; the debugging port is already in use by another instance; the process dies within the first ~2.5s (crash on startup, bad --headless flag for the installed version); sandbox/environment problems (no /dev/shm, missing shared libs in a container); any exception during subprocess creation such as FileNotFoundError.

Common situations: Docker images without a browser installed or with an incompatible browser version; user_data_dir on a read-only volume; leftover browser processes holding the debugging port; SELinux/AppArmor denying exec; CI runners lacking sandbox support requiring --no-sandbox.

Related errors


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