{"record":{"id":"9d7bb0dcc8152d8d","repo":"NanmiCoder/MediaCrawler","slug":"file-not-found","errorCode":null,"errorMessage":"File not found","messagePattern":"File not found","errorType":"http","errorClass":"HTTPException","httpStatus":404,"severity":"critical","filePath":"api/routers/data.py","lineNumber":104,"sourceCode":"\n            try:\n                files.append(get_file_info(file_path))\n            except Exception:\n                continue\n\n    # Sort by modification time (newest first)\n    files.sort(key=lambda x: x[\"modified_at\"], reverse=True)\n\n    return {\"files\": files}\n\n\n@router.get(\"/files/{file_path:path}\")\nasync def get_file_content(file_path: str, preview: bool = True, limit: int = 100):\n    \"\"\"Get file content or preview\"\"\"\n    full_path = DATA_DIR / file_path\n\n    if not full_path.exists():\n        raise HTTPException(status_code=404, detail=\"File not found\")\n\n    if not full_path.is_file():\n        raise HTTPException(status_code=400, detail=\"Not a file\")\n\n    # Security check: ensure within DATA_DIR\n    try:\n        full_path.resolve().relative_to(DATA_DIR.resolve())\n    except ValueError:\n        raise HTTPException(status_code=403, detail=\"Access denied\")\n\n    if preview:\n        # Return preview data\n        try:\n            if full_path.suffix == \".json\":\n                with open(full_path, \"r\", encoding=\"utf-8\") as f:\n                    data = json.load(f)\n                    if isinstance(data, list):\n                        return {\"data\": data[:limit], \"total\": len(data)}","sourceCodeStart":86,"sourceCodeEnd":122,"githubUrl":"https://github.com/NanmiCoder/MediaCrawler/blob/d6f7c5bb906b6dac40ddf343ef9e26438a3de092/api/routers/data.py#L86-L122","documentation":"Raised in _connect_via_cdp when the Playwright CDP attach step did not yield a live connection: either playwright.chromium.connect_over_cdp(ws_url) threw (the outer except logs and re-raises the underlying error), or it returned a browser object whose is_connected() is False. The message is a fallback for the silent-failure branch; most real failures surface as Playwright errors wrapped by the same except block.","triggerScenarios":"connect_over_cdp to a stale/mismatched webSocketDebuggerUrl; the browser exited between fetching the WS URL and connecting; ws_url points at 127.0.0.1 but the browser listens only on ::1 (or vice versa); Playwright's bundled Chromium revision mismatch; DevTools connection rejected because the browser requires --remote-allow-origins for WebSocket clients.","commonSituations":"User closes Chrome while the crawler is connecting; reusing a cached ws_url from a previous browser session; running inside Docker/WSL where localhost mapping differs; Playwright version older than the installed Chrome's CDP dialect; headless Chrome without --remote-debugging-port actually set.","solutions":["Re-fetch the WS URL immediately before connecting (it is already done here) and retry connect_over_cdp once — stale URLs are the most common cause","Ensure Chrome is launched with --remote-debugging-port and, for WebSocket clients sending Origin headers, --remote-allow-origins=* (or the specific origin)","Check self.browser is closed/killed by a previous run: fully stop old chrome --remote-debugging-port processes and remove stale SingletonLock files in the user-data-dir","Upgrade/align playwright (pip install -U playwright && playwright install chromium) so its CDP client matches modern Chrome","Inspect the logged underlying exception above 'CDP connection failed' — the real cause (ECONNREFUSED, 405, handshake timeout) is in that message"],"exampleFix":"// before\nself.browser = await playwright.chromium.connect_over_cdp(ws_url)\nif self.browser.is_connected():\n    ...\nelse:\n    raise RuntimeError(\"CDP connection failed\")\n\n// after\nimport asyncio\nfor attempt in range(3):\n    try:\n        self.browser = await playwright.chromium.connect_over_cdp(ws_url)\n        if self.browser.is_connected():\n            break\n    except Exception:\n        if attempt == 2:\n            raise\n    await asyncio.sleep(2)\n    ws_url = await self._get_browser_websocket_url(self.debug_port)\nelse:\n    raise RuntimeError(\"CDP connection failed\")","handlingStrategy":"retry","validationCode":"async def connect_with_retry(playwright, manager, attempts: int = 3) -> None:\n    for attempt in range(attempts):\n        ws_url = await manager._get_browser_websocket_url(manager.debug_port)\n        try:\n            manager.browser = await playwright.chromium.connect_over_cdp(ws_url)\n            if manager.browser.is_connected():\n                return\n        except Exception:\n            if attempt == attempts - 1:\n                raise\n        await asyncio.sleep(2)","typeGuard":"def is_connected_browser(browser) -> bool:\n    return browser is not None and getattr(browser, \"is_connected\", lambda: False)()","tryCatchPattern":"try:\n    await connect_with_retry(playwright, manager)\nexcept PlaywrightError as e:\n    logger.error(f\"CDP attach failed: {e}\")\n    # full restart: kill browser, relaunch, reconnect — never reuse stale ws_url\n    await manager.stop()\n    await manager.start()","preventionTips":["Always re-fetch the WS URL immediately before connect_over_cdp — never reuse one from a previous browser process","Register browser.on('disconnected') to flag the manager dead instead of limping into later RuntimeError('CDP connection failed')","Launch Chrome with --remote-allow-origins when a WebSocket client with an Origin header connects"],"tags":["cdp","playwright","connection","browser","websocket","retry"],"backgroundTag":null,"analyzedSha":"d6f7c5bb906b6dac40ddf343ef9e26438a3de092","analyzedAt":"2026-08-15T01:39:07.505Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}