srbhr/Resume-Matcher · error · Error

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

Error message

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

What it means

generateCoverLetter() POSTs to /resumes/:id/generate-cover-letter via apiPost and throws on any non-OK response. This endpoint is typically AI/LLM-backed, so failures include both standard auth/validation errors and generation-side failures reported as 4xx/5xx.

Source

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

  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`, {});
  if (!res.ok) {
    const text = await res.text().catch(() => '');
    throw new Error(`Failed to generate outreach message (status ${res.status}): ${text}`);
  }
  const data = await res.json();
  return data.content;
}

/** Generates interview preparation on-demand for a tailored resume */
export async function generateInterviewPrep(resumeId: string): Promise<InterviewPrepData> {

View on GitHub (pinned to 116f9cc3b0)

Solutions

  1. Read the status/body in the message: 429 means rate-limited (wait and retry), 401 means re-authenticate, 404 means check the resume.
  2. If 401: refresh the session and retry generation.
  3. If 429/502/503: retry with exponential backoff; check AI provider status/quota.
  4. If 404/400: verify the resume exists and contains the content generation depends on.
  5. Disable the generate button while in flight and on failure show a retryable error to the user.

Example fix

// before
const content = await generateCoverLetter(resumeId);

// after
try {
  const content = await generateCoverLetter(resumeId);
} catch (err) {
  if (String(err.message).match(/status (429|502|503)/)) {
    await delay(2000);
    return generateCoverLetter(resumeId); // retry once
  }
  notifyUser('Cover letter generation failed. Please try again shortly.');
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (!resumeId) {
  throw new Error('Cannot generate cover letter: resumeId is required');
}

Try / catch

try {
  const { content } = await generateCoverLetterSafe(resumeId);
} catch (err) {
  const msg = err instanceof Error ? err.message : String(err);
  if (/status (429|502|503|504)/.test(msg)) {
    await retryWithBackoff(() => generateCoverLetter(resumeId), 3);
  } else if (msg.includes('status 401')) {
    promptRelogin();
  } else {
    notifyUser('Generation failed. Please try again shortly.');
  }
}

Prevention

When it happens

Trigger: Calling generateCoverLetter(resumeId) when the API returns !res.ok: no resume or tailored content to base generation on (404), expired auth (401), upstream AI provider failure or rate limit surfaced as 429/502/503, or request-body/route mismatch (400/405).

Common situations: LLM provider outage or quota exhaustion causing 429/502; session expired when the user clicks 'generate' (401); resume lacks the base content required for generation (404/400); long generation exceeding a gateway timeout (504).

Related errors


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