srbhr/Resume-Matcher · error · Error

Failed to download cover letter (status ${res.status}): ${te

Error message

Failed to download cover letter (status ${res.status}): ${text}

What it means

downloadCoverLetterPdf() GETs the cover-letter PDF URL via apiFetch and throws when the response is not OK. The status and response body are embedded in the message. Unlike save errors, this path also fails on server-side PDF rendering problems.

Source

Thrown at apps/frontend/lib/api/resume.ts:337

): string {
  const normalizedId = normalizeResumeId(resumeId);
  const params = new URLSearchParams({ pageSize });
  if (locale) {
    params.set('lang', locale);
  }
  return `${API_BASE}/resumes/${encodeURIComponent(normalizedId)}/cover-letter/pdf?${params.toString()}`;
}

export async function downloadCoverLetterPdf(
  resumeId: string,
  pageSize: 'A4' | 'LETTER' = 'A4',
  locale?: Locale
): Promise<Blob> {
  const url = getCoverLetterPdfUrl(resumeId, pageSize, locale);
  const res = await apiFetch(url);
  if (!res.ok) {
    const text = await res.text().catch(() => '');
    throw new Error(`Failed to download cover letter (status ${res.status}): ${text}`);
  }
  return await res.blob();
}

/** Generates a cover letter on-demand for a tailored resume */
export async function generateCoverLetter(resumeId: string): Promise<string> {
  const res = await apiPost(`/resumes/${encodeURIComponent(resumeId)}/generate-cover-letter`, {});
  if (!res.ok) {
    const text = await res.text().catch(() => '');
    throw new Error(`Failed to generate cover letter (status ${res.status}): ${text}`);
  }
  const data = await res.json();
  return data.content;
}

/** Generates an outreach message on-demand for a tailored resume */
export async function generateOutreachMessage(resumeId: string): Promise<string> {
  const res = await apiPost(`/resumes/${encodeURIComponent(resumeId)}/generate-outreach`, {});

View on GitHub (pinned to 116f9cc3b0)

Solutions

  1. Check the embedded status/body: 404 means no cover letter exists yet; 401 means re-authenticate.
  2. If 404: generate the cover letter first (generateCoverLetter) and then retry the download.
  3. If 401: refresh the session and retry.
  4. If 5xx/504: retry after a short delay; investigate the PDF renderer logs if persistent.
  5. Surface a user-facing 'download failed' message and keep the button enabled for retry.

Example fix

// before
const blob = await downloadCoverLetterPdf(resumeId, pageSize, locale);

// after
let blob;
try {
  blob = await downloadCoverLetterPdf(resumeId, pageSize, locale);
} catch (err) {
  if (String(err.message).includes('status 404')) {
    await generateCoverLetter(resumeId);
    blob = await downloadCoverLetterPdf(resumeId, pageSize, locale);
  } else {
    notifyUser('Cover letter download failed. Please try again.');
  }
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (!resumeId) {
  throw new Error('Cannot download cover letter: resumeId is required');
}
const allowedPageSizes = ['a4', 'letter'];
if (pageSize && !allowedPageSizes.includes(pageSize)) {
  throw new Error(`Unsupported pageSize: ${pageSize}`);
}

Try / catch

try {
  const blob = await downloadCoverLetterPdf(resumeId, pageSize, locale);
  triggerBlobDownload(blob);
} catch (err) {
  const msg = err instanceof Error ? err.message : String(err);
  if (msg.includes('status 404')) notifyUser('No cover letter yet — generate one first.');
  else if (msg.includes('status 401')) promptRelogin();
  else notifyUser('Download failed. Please retry.');
}

Prevention

When it happens

Trigger: Calling downloadCoverLetterPdf(resumeId, pageSize, locale) when the API returns !res.ok: resume or cover letter missing (404), expired auth (401), unsupported pageSize/locale (400), or PDF generation failure (500).

Common situations: User clicks download before a cover letter has been generated (404); session expired (401); backend PDF renderer (e.g. headless browser) crashes or times out producing 5xx; gateway timeout on large documents (504).

Related errors


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