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
- Generate the cover letter for the resume first, then retry the PDF download.
- If a cover letter existed, check generation logs for a failed/aborted save that left the field empty.
- 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
- Gate the PDF download button on cover_letter being present in the resume object.
- Verify cover-letter generation persisted successfully before marking it complete in the UI.
- Backfill cover_letter for legacy resumes if the feature was added later.
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
- Failed to download resume (status ${res.status}): ${text}
- Failed to download cover letter (status ${res.status}): ${te
- Playwright browser executable is missing, and no system Chro
- Playwright browser executable is missing or out of date. Com
- Cannot connect to frontend for PDF generation. Attempted URL
AI-assisted analysis of srbhr/Resume-Matcher@116f9cc3b0 (2026-08-28).
Data as JSON: /api/errors/436300981fc76c85.
Report an issue: GitHub.