srbhr/Resume-Matcher · error · PDFRenderError
Cannot connect to frontend for PDF generation. Attempted URL
Error message
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. What it means
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.
Source
Thrown at apps/backend/app/pdf.py:240
pdf_margins: dict,
) -> bytes:
return await asyncio.to_thread(
_render_resume_pdf_sync, url, selector, pdf_format, pdf_margins
)
def _raise_playwright_error(error: PlaywrightError, url: str) -> NoReturn:
error_msg = str(error)
if "Executable doesn't exist" in error_msg:
exe = sys.executable.replace("\\", "/")
command = f"{exe} -m playwright install chromium"
raise PDFRenderError(
"Playwright browser executable is missing or out of date. "
"Command shown for reference; quote the path if it contains spaces: "
f"{command}"
) from error
if "net::ERR_CONNECTION_REFUSED" in error_msg:
raise PDFRenderError(
f"Cannot connect to frontend for PDF generation. "
f"Attempted URL: {url}. "
f"Please ensure: 1) The frontend is running, "
f"2) The FRONTEND_BASE_URL environment variable in the backend .env file "
f"matches the URL where your frontend is accessible."
) from error
# Catch-all: the raw Playwright message can carry internal navigation URLs
# and a full call log. Log it server-side; return a generic message to the
# client (CLAUDE.md rule 5 — and it stops the verbose trace from overflowing
# the client error modal, #811).
logger.error("PDF rendering failed for %s: %s", url, error_msg)
raise PDFRenderError(
"PDF rendering failed. Please try again, or try a simpler resume or a "
"different template."
) from error
def _loop_supports_subprocess() -> bool:View on GitHub (pinned to 116f9cc3b0)
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
Example fix
// before (.env) FRONTEND_BASE_URL=http://localhost:5173 // after (backend in Docker) FRONTEND_BASE_URL=http://host.docker.internal:5173
Defensive patterns
Strategy: try-catch
Validate before calling
import os, socket
from urllib.parse import urlparse
def frontend_reachable() -> bool:
u = urlparse(os.environ["FRONTEND_BASE_URL"])
try:
with socket.create_connection((u.hostname, u.port or 80), timeout=2):
return True
except OSError:
return False Type guard
def is_connection_refused(err: Exception) -> bool:
return "net::ERR_CONNECTION_REFUSED" in str(err) Try / catch
try:
pdf = await render_resume_pdf(url, selector)
except PDFRenderError as e:
if "Cannot connect to frontend" in str(e):
health = await check_frontend_health()
raise RuntimeError(f"Frontend unreachable at {health.url}; check FRONTEND_BASE_URL") from e
raise Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Failed to load prompt config (status ${res.status}).
- Failed to load feature prompts (status ${res.status}).
- Playwright browser executable is missing, and no system Chro
- Playwright browser executable is missing or out of date. Com
- PDF rendering failed. Please try again, or try a simpler res
AI-assisted analysis of srbhr/Resume-Matcher@116f9cc3b0 (2026-08-28).
Data as JSON: /api/errors/7ed6932575925fdc.
Report an issue: GitHub.