{"record":{"id":"7dbf8a0187511a04","repo":"srbhr/Resume-Matcher","slug":"failed-to-generate-outreach-message-status-res","errorCode":null,"errorMessage":"Failed to generate outreach message (status ${res.status}): ${text}","messagePattern":"Failed to generate outreach message \\(status (.+?)\\): (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/frontend/lib/api/resume.ts","lineNumber":358,"sourceCode":"}\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> {\n  const res = await apiPost(`/resumes/${encodeURIComponent(resumeId)}/generate-interview-prep`, {});\n  if (!res.ok) {\n    const text = await res.text().catch(() => '');\n    throw new Error(`Failed to generate interview preparation (status ${res.status}): ${text}`);\n  }\n  const data = (await res.json()) as { interview_prep: InterviewPrepData };\n  return data.interview_prep;\n}\n\n/** Retries AI processing for a failed resume */\nexport async function retryProcessing(resumeId: string): Promise<ResumeUploadResponse> {","sourceCodeStart":340,"sourceCodeEnd":376,"githubUrl":"https://github.com/srbhr/Resume-Matcher/blob/116f9cc3b00e1ac91734a6c2679bf41ea64a0edc/apps/frontend/lib/api/resume.ts#L340-L376","documentation":"generateOutreachMessage calls POST /resumes/{id}/generate-outreach and throws this Error whenever the backend responds with a non-OK HTTP status. The response body text (often a JSON error detail) is embedded in the message to expose the server-side reason. This is a client-side guard converting an API failure into a thrown exception for the caller to handle.","triggerScenarios":"Any non-2xx response from the generate-outreach endpoint: resume not found (404), AI generation failure or upstream LLM timeout (500/502/504), unauthenticated/expired session (401), or invalid resume id causing a backend validation rejection.","commonSituations":"Developers hit this when a resume is still processing or failed AI enrichment and outreach generation is requested, when the session token expired, when the backend LLM provider is down/rate-limited, or when a stale resumeId is used from a deleted record.","solutions":["Check the embedded status/text in the message to identify the server reason (401/404/500 etc.)","Verify the resumeId exists and belongs to the authenticated user","Ensure the resume finished processing successfully before requesting outreach generation","Re-authenticate the frontend session if status is 401/403","Retry later if the backend AI provider is rate-limited or down (5xx)","Inspect backend logs for the generate-outreach handler failure"],"exampleFix":"// before\ndata.content = await generateOutreachMessage(resumeId);\n// after\ntry {\n  data.content = await generateOutreachMessage(resumeId);\n} catch (e) {\n  showToast('Could not generate outreach message. Please retry once the resume is ready.');\n  data.content = null;\n}","handlingStrategy":"try-catch","validationCode":"// Pre-check the resume is ready before calling\nconst status = await getResumeStatus(resumeId);\nif (status.state !== 'completed') {\n  throw new Error(`Resume ${resumeId} is not ready for outreach generation (state: ${status.state})`);\n}\nif (!resumeId || resumeId.trim() === '') {\n  throw new Error('resumeId is required');\n}","typeGuard":"function isHttpErrorWithStatus(e: unknown): e is Error & { status?: number } {\n  const m = e instanceof Error ? e.message.match(/status (\\d{3})/) : null;\n  return e instanceof Error && m !== null;\n}","tryCatchPattern":"try {\n  const content = await generateOutreachMessage(resumeId);\n} catch (e) {\n  if (isHttpErrorWithStatus(e) && e.message.includes('status 401')) {\n    redirectToLogin();\n  } else {\n    showError('Outreach generation failed. Verify the resume is processed, then retry.');\n  }\n}","preventionTips":["Gate outreach generation behind resume status === 'completed'","Handle 401 globally by refreshing the session before API calls","Retry 5xx with backoff; never retry 4xx","Show the embedded status/text to the user for diagnosis","Disable the outreach button while the resume is still processing"],"tags":["http-error","api","fetch","frontend"],"backgroundTag":"http-non-2xx-response","analyzedSha":"116f9cc3b00e1ac91734a6c2679bf41ea64a0edc","analyzedAt":"2026-08-28T22:51:40.999Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}