srbhr/Resume-Matcher · error · PDFRenderError

str(e) (message from PDFRenderError)

Error message

str(e) (message from PDFRenderError)

What it means

HTTP 503 raised when render_resume_pdf fails with PDFRenderError while snapshotting the cover-letter print page headlessly (e.g. Playwright/Puppeteer). The raw renderer message (str(e)) is passed through as the detail. 503 signals the rendering service is temporarily unavailable or the page failed to render.

Source

Thrown at apps/backend/app/routers/resumes.py:2055

    cover_letter = resume.get("cover_letter")
    if not cover_letter:
        raise HTTPException(
            status_code=404, detail="No cover letter found for this resume"
        )

    # Build print URL (same pattern as resume PDF)
    url = f"{settings.frontend_base_url}/print/cover-letter/{resume_id}?pageSize={pageSize}"
    if lang:
        url = f"{url}&lang={lang}"

    # Render PDF with cover letter selector
    try:
        pdf_bytes = await render_resume_pdf(
            url, pageSize, selector=".cover-letter-print"
        )
    except PDFRenderError as e:
        raise HTTPException(status_code=503, detail=str(e))

    headers = {
        "Content-Disposition": f'attachment; filename="cover_letter_{resume_id}.pdf"'
    }
    return Response(content=pdf_bytes, media_type="application/pdf", headers=headers)

View on GitHub (pinned to 116f9cc3b0)

Solutions

  1. Check the PDFRenderError message in the 503 response/logs — fix the specific renderer cause (timeout, navigation, selector).
  2. Verify settings.frontend_base_url is reachable from the backend container (curl the print URL).
  3. Ensure the print route /print/cover-letter exists in the deployed frontend and the .cover-letter-print selector is present.
  4. In Docker, install headless-browser system dependencies and confirm the browser binary launches.
  5. Retry — if the renderer was temporarily saturated, a retry with backoff often succeeds.

Example fix

// before
except PDFRenderError as e:
    raise HTTPException(status_code=503, detail=str(e))
// after (retry transient render failures)
except PDFRenderError as e:
    if e.is_transient:
        pdf_bytes = await render_resume_pdf(url, pageSize, selector=".cover-letter-print")
    else:
        raise HTTPException(status_code=503, detail=str(e))
Defensive patterns

Strategy: retry

Validate before calling

const printUrl = `${frontendBaseUrl}/print/cover-letter/${resumeId}`;
const ok = await fetch(printUrl).then(r => r.ok).catch(() => false);
if (!ok) throw new SkipError('print page unreachable — renderer would fail');

Try / catch

try {
  return await withBackoff(() => downloadCoverLetterPdf(resumeId), {retries: 2});
} catch (e) {
  if (e.status === 503) {
    showStatus('PDF renderer temporarily unavailable: ' + e.detail + '. Check frontend_base_url and try again.');
    return null;
  }
  throw e;
}

Prevention

When it happens

Trigger: The headless browser fails to load {frontend_base_url}/print/cover-letter/{id} — frontend unreachable, page JS error, selector .cover-letter-print absent, navigation timeout, or browser process launch failure.

Common situations: frontend_base_url misconfigured (wrong host/port, http vs https); print route not deployed in the backend's target environment; browser sandbox missing dependencies in Docker (e.g. libnss3); cover-letter content causing client-side render crash; timeouts on large documents.

Related errors


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