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

  1. Start the frontend dev server (or confirm it is listening) at the URL in FRONTEND_BASE_URL
  2. Check FRONTEND_BASE_URL in the backend .env — host and port must match where the frontend is actually reachable
  3. If the backend runs in Docker, replace localhost with host.docker.internal (or the service name on the compose network)
  4. Curl the URL from the backend's environment to confirm reachability before rendering
  5. 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

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


AI-assisted analysis of srbhr/Resume-Matcher@116f9cc3b0 (2026-08-28). Data as JSON: /api/errors/7ed6932575925fdc. Report an issue: GitHub.