srbhr/Resume-Matcher · error · HTTPException

No cover letter found for this resume

Error message

No cover letter found for this resume

What it means

HTTP 404 raised when the resume exists but has no cover_letter field/content. The PDF renderer needs stored cover-letter content to build the print page; without it there is nothing to render, so the endpoint returns 404 rather than an empty PDF.

Source

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

async def download_cover_letter_pdf(
    resume_id: str,
    pageSize: str = Query("A4", pattern="^(A4|LETTER)$"),
    lang: str | None = Query(None, pattern="^[a-z]{2}(-[A-Z]{2})?$"),
) -> Response:
    """Generate a PDF for a cover letter using headless Chromium.

    Args:
        resume_id: The ID of the resume containing the cover letter
        pageSize: A4 or LETTER
        lang: locale used for print page translations
    """
    resume = await db.get_resume(resume_id)
    if not resume:
        raise HTTPException(status_code=404, detail="Resume not found")

    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"'

View on GitHub (pinned to 116f9cc3b0)

Solutions

  1. Generate the cover letter for the resume first, then retry the PDF download.
  2. If a cover letter existed, check generation logs for a failed/aborted save that left the field empty.
  3. Gate the download button in the UI on cover_letter being present.

Example fix

// before
cover_letter = resume.get("cover_letter")
if not cover_letter:
    raise HTTPException(status_code=404, detail="No cover letter found for this resume")
// after (client-side gate)
const canDownload = Boolean(resume.cover_letter);
downloadButton.disabled = !canDownload;
Defensive patterns

Strategy: validation

Validate before calling

const resume = await getResume(id);
if (!resume) throw new SkipError('resume missing');
if (!resume.cover_letter) {
  await generateCoverLetter(id); // create content before downloading PDF
}

Type guard

function hasCoverLetter(r) {
  return r != null && typeof r.cover_letter === 'string' && r.cover_letter.trim().length > 0;
}

Try / catch

try {
  return await downloadCoverLetterPdf(resumeId);
} catch (e) {
  if (e.status === 404 && /cover letter/i.test(e.detail)) {
    await generateCoverLetter(resumeId);
    return downloadCoverLetterPdf(resumeId); // retry once after generating
  }
  throw e;
}

Prevention

When it happens

Trigger: Request the cover-letter PDF for a resume whose cover letter was never generated or was cleared — resume.get("cover_letter") is falsy.

Common situations: User never generated a cover letter for that resume; a regeneration/edit cleared the field; older resume records predating the cover_letter schema; partial save during generation failure.

Related errors


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