srbhr/Resume-Matcher · error · Error

Failed to generate outreach message (status ${res.status}):

Error message

Failed to generate outreach message (status ${res.status}): ${text}

What it means

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.

Source

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

}

/** 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> {
  const res = await apiPost(`/resumes/${encodeURIComponent(resumeId)}/generate-interview-prep`, {});
  if (!res.ok) {
    const text = await res.text().catch(() => '');
    throw new Error(`Failed to generate interview preparation (status ${res.status}): ${text}`);
  }
  const data = (await res.json()) as { interview_prep: InterviewPrepData };
  return data.interview_prep;
}

/** Retries AI processing for a failed resume */
export async function retryProcessing(resumeId: string): Promise<ResumeUploadResponse> {

View on GitHub (pinned to 116f9cc3b0)

Solutions

  1. Check the embedded status/text in the message to identify the server reason (401/404/500 etc.)
  2. Verify the resumeId exists and belongs to the authenticated user
  3. Ensure the resume finished processing successfully before requesting outreach generation
  4. Re-authenticate the frontend session if status is 401/403
  5. Retry later if the backend AI provider is rate-limited or down (5xx)
  6. Inspect backend logs for the generate-outreach handler failure

Example fix

// before
data.content = await generateOutreachMessage(resumeId);
// after
try {
  data.content = await generateOutreachMessage(resumeId);
} catch (e) {
  showToast('Could not generate outreach message. Please retry once the resume is ready.');
  data.content = null;
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-check the resume is ready before calling
const status = await getResumeStatus(resumeId);
if (status.state !== 'completed') {
  throw new Error(`Resume ${resumeId} is not ready for outreach generation (state: ${status.state})`);
}
if (!resumeId || resumeId.trim() === '') {
  throw new Error('resumeId is required');
}

Type guard

function isHttpErrorWithStatus(e: unknown): e is Error & { status?: number } {
  const m = e instanceof Error ? e.message.match(/status (\d{3})/) : null;
  return e instanceof Error && m !== null;
}

Try / catch

try {
  const content = await generateOutreachMessage(resumeId);
} catch (e) {
  if (isHttpErrorWithStatus(e) && e.message.includes('status 401')) {
    redirectToLogin();
  } else {
    showError('Outreach generation failed. Verify the resume is processed, then retry.');
  }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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