{"record":{"id":"e7f59960740aeef5","repo":"unclecode/crawl4ai","slug":"cdp-endpoint-at-cdp-url-is-not-ready-after-start","errorCode":null,"errorMessage":"CDP endpoint at {cdp_url} is not ready after startup","messagePattern":"CDP endpoint at (.+?) is not ready after startup","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"critical","filePath":"crawl4ai/browser_manager.py","lineNumber":889,"sourceCode":"            self._launched_persistent = True\n\n            await self.setup_context(self.default_context)\n\n            # Set the browser endpoint key for global page tracking\n            self._browser_endpoint_key = self._compute_browser_endpoint_key()\n            if self._browser_endpoint_key not in BrowserManager._global_pages_in_use:\n                BrowserManager._global_pages_in_use[self._browser_endpoint_key] = set()\n            return\n\n        if self.config.cdp_url or self.config.use_managed_browser:\n            self.config.use_managed_browser = True\n\n            if not self._using_cached_cdp:\n                cdp_url = await self.managed_browser.start() if not self.config.cdp_url else self.config.cdp_url\n\n                # Add CDP endpoint verification before connecting\n                if not await self._verify_cdp_ready(cdp_url):\n                    raise Exception(f\"CDP endpoint at {cdp_url} is not ready after startup\")\n\n                self.browser = await self.playwright.chromium.connect_over_cdp(cdp_url)\n\n            contexts = self.browser.contexts\n\n            # If browser_context_id is provided, we're using a pre-created context\n            if self.config.browser_context_id:\n                if self.logger:\n                    self.logger.debug(\n                        f\"Using pre-existing browser context: {self.config.browser_context_id}\",\n                        tag=\"BROWSER\"\n                    )\n                # When connecting to a pre-created context, it should be in contexts\n                if contexts:\n                    self.default_context = contexts[0]\n                    if self.logger:\n                        self.logger.debug(\n                            f\"Found {len(contexts)} existing context(s), using first one\",","sourceCodeStart":871,"sourceCodeEnd":907,"githubUrl":"https://github.com/unclecode/crawl4ai/blob/7e801521428ee12509994d39151006f64055ebe3/crawl4ai/browser_manager.py#L871-L907","documentation":"When connecting over CDP (config.cdp_url set, or use_managed_browser=True), BrowserManager probes the endpoint with _verify_cdp_ready(cdp_url) before calling playwright.chromium.connect_over_cdp. If the DevTools endpoint does not answer within its retry window, this Exception aborts connection. Note it fires before any Playwright error, so the real state is: nothing is listening / not ready at that URL.","triggerScenarios":"config.cdp_url='http://localhost:9222' but no Chrome started with --remote-debugging-port=9222; the managed browser subprocess crashed right after start; CDP bound to 127.0.0.1 while you connect to another host/port; firewall or wrong port; Chrome started with the new --remote-debugging-pipe instead of port.","commonSituations":"Connecting to an external/pre-launched Chrome that was started without the debugging flag; race where the managed browser is still booting; Docker port-mapping mismatches; Chrome 111+ requiring --remote-debugging-port explicitly plus a non-default user-data-dir.","solutions":["Start Chrome with debugging enabled and a separate profile: chrome --remote-debugging-port=9222 --user-data-dir=/tmp/chrome-debug, then point cdp_url at http://localhost:9222.","Verify the endpoint yourself: curl http://localhost:9222/json/version — it must return JSON before you start the crawler.","If using the managed browser (no cdp_url), check that the browser binary launched at all (see the 'Failed to start browser' error) and that debugging_port is free.","Fix host/port in cdp_url (scheme http, correct container hostnames in docker-compose)."],"exampleFix":"# before\nBrowserConfig(cdp_url='http://localhost:9222')  # nothing listening -> not ready\n\n# after\n# terminal: google-chrome --remote-debugging-port=9222 --user-data-dir=/tmp/chrome-debug\n# curl http://localhost:9222/json/version  # sanity check\nBrowserConfig(cdp_url='http://localhost:9222')","handlingStrategy":"retry","validationCode":"import urllib.request, json\ndef cdp_ready(cdp_url: str, timeout: float = 2.0) -> bool:\n    try:\n        with urllib.request.urlopen(f'{cdp_url.rstrip(\"/\")}/json/version', timeout=timeout) as r:\n            return r.status == 200 and json.loads(r.read()).get('Browser')\n    except Exception:\n        return False","typeGuard":null,"tryCatchPattern":"try:\n    async with AsyncWebCrawler(config=BrowserConfig(cdp_url=cdp_url)) as c:\n        ...\nexcept Exception as e:\n    if 'not ready after startup' in str(e):\n        # wait for the external Chrome to boot and retry once\n        await asyncio.sleep(5)\n        async with AsyncWebCrawler(config=BrowserConfig(cdp_url=cdp_url)) as c:\n            ...\n    else:\n        raise","preventionTips":["Health-check /json/version before creating the crawler when using cdp_url.","Start external Chrome with --remote-debugging-port and a dedicated --user-data-dir.","Retry once with backoff to absorb browser boot races."],"tags":["browser-manager","cdp","network","startup"],"backgroundTag":null,"analyzedSha":"7e801521428ee12509994d39151006f64055ebe3","analyzedAt":"2026-08-14T20:46:20.673Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}