{"record":{"id":"181b9764d7e64682","repo":"srbhr/Resume-Matcher","slug":"failed-to-generate-cover-letter-status-res-stat","errorCode":null,"errorMessage":"Failed to generate cover letter (status ${res.status}): ${text}","messagePattern":"Failed to generate cover letter \\(status (.+?)\\): (.+?)","errorType":"http","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/frontend/lib/api/resume.ts","lineNumber":347,"sourceCode":"  resumeId: string,\n  pageSize: 'A4' | 'LETTER' = 'A4',\n  locale?: Locale\n): Promise<Blob> {\n  const url = getCoverLetterPdfUrl(resumeId, pageSize, locale);\n  const res = await apiFetch(url);\n  if (!res.ok) {\n    const text = await res.text().catch(() => '');\n    throw new Error(`Failed to download cover letter (status ${res.status}): ${text}`);\n  }\n  return await res.blob();\n}\n\n/** Generates a cover letter on-demand for a tailored resume */\nexport async function generateCoverLetter(resumeId: string): Promise<string> {\n  const res = await apiPost(`/resumes/${encodeURIComponent(resumeId)}/generate-cover-letter`, {});\n  if (!res.ok) {\n    const text = await res.text().catch(() => '');\n    throw new Error(`Failed to generate cover letter (status ${res.status}): ${text}`);\n  }\n  const data = await res.json();\n  return data.content;\n}\n\n/** Generates an outreach message on-demand for a tailored resume */\nexport async function generateOutreachMessage(resumeId: string): Promise<string> {\n  const res = await apiPost(`/resumes/${encodeURIComponent(resumeId)}/generate-outreach`, {});\n  if (!res.ok) {\n    const text = await res.text().catch(() => '');\n    throw new Error(`Failed to generate outreach message (status ${res.status}): ${text}`);\n  }\n  const data = await res.json();\n  return data.content;\n}\n\n/** Generates interview preparation on-demand for a tailored resume */\nexport async function generateInterviewPrep(resumeId: string): Promise<InterviewPrepData> {","sourceCodeStart":329,"sourceCodeEnd":365,"githubUrl":"https://github.com/srbhr/Resume-Matcher/blob/116f9cc3b00e1ac91734a6c2679bf41ea64a0edc/apps/frontend/lib/api/resume.ts#L329-L365","documentation":"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.","triggerScenarios":"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).","commonSituations":"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).","solutions":["Read the status/body in the message: 429 means rate-limited (wait and retry), 401 means re-authenticate, 404 means check the resume.","If 401: refresh the session and retry generation.","If 429/502/503: retry with exponential backoff; check AI provider status/quota.","If 404/400: verify the resume exists and contains the content generation depends on.","Disable the generate button while in flight and on failure show a retryable error to the user."],"exampleFix":"// before\nconst content = await generateCoverLetter(resumeId);\n\n// after\ntry {\n  const content = await generateCoverLetter(resumeId);\n} catch (err) {\n  if (String(err.message).match(/status (429|502|503)/)) {\n    await delay(2000);\n    return generateCoverLetter(resumeId); // retry once\n  }\n  notifyUser('Cover letter generation failed. Please try again shortly.');\n}","handlingStrategy":"try-catch","validationCode":"if (!resumeId) {\n  throw new Error('Cannot generate cover letter: resumeId is required');\n}","typeGuard":null,"tryCatchPattern":"try {\n  const { content } = await generateCoverLetterSafe(resumeId);\n} catch (err) {\n  const msg = err instanceof Error ? err.message : String(err);\n  if (/status (429|502|503|504)/.test(msg)) {\n    await retryWithBackoff(() => generateCoverLetter(resumeId), 3);\n  } else if (msg.includes('status 401')) {\n    promptRelogin();\n  } else {\n    notifyUser('Generation failed. Please try again shortly.');\n  }\n}","preventionTips":["Implement retry with exponential backoff for 429/5xx from AI-backed endpoints.","Disable the generate button while a request is in flight to avoid duplicate calls.","Refresh sessions before long generation workflows to avoid 401s.","Set a client-side timeout slightly below the gateway timeout and treat timeouts as retryable."],"tags":["http","api","generation","ai","frontend"],"backgroundTag":"http-non-ok-response","analyzedSha":"116f9cc3b00e1ac91734a6c2679bf41ea64a0edc","analyzedAt":"2026-08-28T22:51:40.999Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}