{"record":{"id":"7ed6932575925fdc","repo":"srbhr/Resume-Matcher","slug":"cannot-connect-to-frontend-for-pdf-generation-att","errorCode":null,"errorMessage":"Cannot connect to frontend for PDF generation. Attempted URL: {url}. Please ensure: 1) The frontend is running, 2) The FRONTEND_BASE_URL environment variable in the backend .env file matches the URL where your frontend is accessible.","messagePattern":"Cannot connect to frontend for PDF generation\\. Attempted URL: (.+?)\\. Please ensure: 1\\) The frontend is running, 2\\) The FRONTEND_BASE_URL environment variable in the backend \\.env file matches the URL where your frontend is accessible\\.","errorType":"exception","errorClass":"PDFRenderError","httpStatus":null,"severity":"error","filePath":"apps/backend/app/pdf.py","lineNumber":240,"sourceCode":"    pdf_margins: dict,\n) -> bytes:\n    return await asyncio.to_thread(\n        _render_resume_pdf_sync, url, selector, pdf_format, pdf_margins\n    )\n\n\ndef _raise_playwright_error(error: PlaywrightError, url: str) -> NoReturn:\n    error_msg = str(error)\n    if \"Executable doesn't exist\" in error_msg:\n        exe = sys.executable.replace(\"\\\\\", \"/\")\n        command = f\"{exe} -m playwright install chromium\"\n        raise PDFRenderError(\n            \"Playwright browser executable is missing or out of date. \"\n            \"Command shown for reference; quote the path if it contains spaces: \"\n            f\"{command}\"\n        ) from error\n    if \"net::ERR_CONNECTION_REFUSED\" in error_msg:\n        raise PDFRenderError(\n            f\"Cannot connect to frontend for PDF generation. \"\n            f\"Attempted URL: {url}. \"\n            f\"Please ensure: 1) The frontend is running, \"\n            f\"2) The FRONTEND_BASE_URL environment variable in the backend .env file \"\n            f\"matches the URL where your frontend is accessible.\"\n        ) from error\n    # Catch-all: the raw Playwright message can carry internal navigation URLs\n    # and a full call log. Log it server-side; return a generic message to the\n    # client (CLAUDE.md rule 5 — and it stops the verbose trace from overflowing\n    # the client error modal, #811).\n    logger.error(\"PDF rendering failed for %s: %s\", url, error_msg)\n    raise PDFRenderError(\n        \"PDF rendering failed. Please try again, or try a simpler resume or a \"\n        \"different template.\"\n    ) from error\n\n\ndef _loop_supports_subprocess() -> bool:","sourceCodeStart":222,"sourceCodeEnd":258,"githubUrl":"https://github.com/srbhr/Resume-Matcher/blob/116f9cc3b00e1ac91734a6c2679bf41ea64a0edc/apps/backend/app/pdf.py#L222-L258","documentation":"PDFRenderError raised by _raise_playwright_error when the headless browser navigation fails with net::ERR_CONNECTION_REFUSED. The backend could not open a TCP connection to the frontend URL it renders, so the error is translated into an actionable message naming the attempted URL and the FRONTEND_BASE_URL env var.","triggerScenarios":"render_resume_pdf launches Chromium and navigates to `url` (built from FRONTEND_BASE_URL); the target port is closed — frontend not running, wrong port/host in FRONTEND_BASE_URL, or the backend runs in a container where localhost refers to the container itself.","commonSituations":"Frontend dev server not started; FRONTEND_BASE_URL in backend .env points at the wrong port (e.g. 3000 vs 5173); backend in Docker using http://localhost:... instead of http://host.docker.internal:...; frontend bound to 127.0.0.1 while backend tries a LAN hostname; frontend briefly restarted while a PDF was requested.","solutions":["Start the frontend dev server (or confirm it is listening) at the URL in FRONTEND_BASE_URL","Check FRONTEND_BASE_URL in the backend .env — host and port must match where the frontend is actually reachable","If the backend runs in Docker, replace localhost with host.docker.internal (or the service name on the compose network)","Curl the URL from the backend's environment to confirm reachability before rendering","Add a readiness/health check so PDF requests only proceed once the frontend responds"],"exampleFix":"// before (.env)\nFRONTEND_BASE_URL=http://localhost:5173\n// after (backend in Docker)\nFRONTEND_BASE_URL=http://host.docker.internal:5173","handlingStrategy":"try-catch","validationCode":"import os, socket\nfrom urllib.parse import urlparse\ndef frontend_reachable() -> bool:\n    u = urlparse(os.environ[\"FRONTEND_BASE_URL\"])\n    try:\n        with socket.create_connection((u.hostname, u.port or 80), timeout=2):\n            return True\n    except OSError:\n        return False","typeGuard":"def is_connection_refused(err: Exception) -> bool:\n    return \"net::ERR_CONNECTION_REFUSED\" in str(err)","tryCatchPattern":"try:\n    pdf = await render_resume_pdf(url, selector)\nexcept PDFRenderError as e:\n    if \"Cannot connect to frontend\" in str(e):\n        health = await check_frontend_health()\n        raise RuntimeError(f\"Frontend unreachable at {health.url}; check FRONTEND_BASE_URL\") from e\n    raise","preventionTips":["Smoke-test FRONTEND_BASE_URL with curl at backend startup","In Docker, use host.docker.internal or compose service names, never localhost","Add a startup health check that refuses PDF jobs until the frontend responds","Keep the frontend and backend ports documented in .env.example","Alert on this error — it signals an environment/configuration drift"],"tags":["pdf","playwright","network","connection-refused","config"],"backgroundTag":"connection-refused","analyzedSha":"116f9cc3b00e1ac91734a6c2679bf41ea64a0edc","analyzedAt":"2026-08-28T22:51:40.999Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}