{"record":{"id":"db6032cbaed51146","repo":"srbhr/Resume-Matcher","slug":"data-detail-failed-to-analyze-resume-status","errorCode":null,"errorMessage":"${data.detail || Failed to analyze resume (status ${res.status}).}","messagePattern":"\\$\\{data\\.detail \\|\\| Failed to analyze resume \\(status \\$\\{res\\.status\\}\\)\\.\\}","errorType":"http","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/frontend/lib/api/enrichment.ts","lineNumber":60,"sourceCode":"}\n\nexport interface EnhancementPreview {\n  enhancements: EnhancedDescription[];\n}\n\n/**\n * Analyze a resume to identify items that need enrichment.\n * Returns items with weak descriptions and clarifying questions.\n */\nexport async function analyzeResume(resumeId: string): Promise<AnalysisResponse> {\n  const res = await apiFetch(`/enrichment/analyze/${resumeId}`, {\n    method: 'POST',\n    credentials: 'include',\n  });\n\n  if (!res.ok) {\n    const data = await res.json().catch(() => ({}));\n    throw new Error(data.detail || `Failed to analyze resume (status ${res.status}).`);\n  }\n\n  return res.json();\n}\n\n/**\n * Generate enhanced descriptions from user answers.\n */\nexport async function generateEnhancements(\n  resumeId: string,\n  answers: AnswerInput[]\n): Promise<EnhancementPreview> {\n  const res = await apiPost('/enrichment/enhance', {\n    resume_id: resumeId,\n    answers,\n  });\n\n  if (!res.ok) {","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/srbhr/Resume-Matcher/blob/116f9cc3b00e1ac91734a6c2679bf41ea64a0edc/apps/frontend/lib/api/enrichment.ts#L42-L78","documentation":"analyzeResume in apps/frontend/lib/api/enrichment.ts throws this Error when the POST to the backend resume-analysis endpoint returns a non-OK HTTP response. It first tries to read the JSON body and surface the server-provided `detail` message (FastAPI-style error payloads); if the body is not valid JSON, it falls back to a generic message embedding the HTTP status code. This is a client-side wrapper around any 4xx/5xx backend response during AI resume analysis.","triggerScenarios":"The `fetch` POST (with credentials: 'include') resolves with res.ok === false: e.g. 401 when the auth cookie is missing/expired, 404 when resumeId does not exist server-side, 422 when the resumeId format is invalid, 429 rate limiting on the AI analysis backend, or 500/502/503 when the AI analysis service fails or times out.","commonSituations":"User session expired so cookies are no longer valid (401); the resume was deleted in another tab so the ID is stale (404); the backend LLM provider key is missing/out of quota causing 500; the frontend is pointed at the wrong API base URL so the route 404s; a proxy/gateway returns an HTML error page so res.json() fails and the fallback message appears.","solutions":["Log or inspect res.status and the response body to identify whether it is 401/404/429/5xx before changing code.","If 401: re-authenticate the user / refresh the session cookie before calling analyzeResume (credentials: 'include' only sends cookies that exist).","If 404: verify the resumeId is valid and still exists (fetch the resume list first); do not analyze a deleted resume.","If 5xx: check backend logs for the analysis/AI-provider failure (API keys, quota, timeout) and add a retry with backoff for transient 502/503/429.","Confirm NEXT_PUBLIC_API_BASE_URL / proxy config points at the running backend so the route resolves."],"exampleFix":"// before\nconst res = await fetch(`${API_URL}/enrichment/analyze/${resumeId}`, { method: 'POST', credentials: 'include' });\nif (!res.ok) {\n  const data = await res.json().catch(() => ({}));\n  throw new Error(data.detail || `Failed to analyze resume (status ${res.status}).`);\n}\n// after\nconst res = await fetch(`${API_URL}/enrichment/analyze/${resumeId}`, { method: 'POST', credentials: 'include' });\nif (res.status === 401) throw new Error('Your session expired. Please sign in again.');\nif (!res.ok) {\n  const data = await res.json().catch(() => ({}));\n  throw new Error(data.detail || `Failed to analyze resume (status ${res.status}).`);\n}","handlingStrategy":"try-catch","validationCode":"if (!resumeId || !resumeId.trim()) throw new Error('A valid resumeId is required before analyzing.');","typeGuard":"function isAnalysisError(e: unknown): e is Error & { status?: number } {\n  return e instanceof Error && e.message.includes('Failed to analyze resume');\n}","tryCatchPattern":"try {\n  const analysis = await analyzeResume(resumeId);\n} catch (e) {\n  if (isAnalysisError(e) && /status 401/.test(e.message)) {\n    redirectToLogin();\n  } else {\n    showToast(e instanceof Error ? e.message : 'Analysis failed. Please try again.');\n  }\n}","preventionTips":["Always call analyzeResume with a resumeId verified to exist (from the resume list) rather than cached state.","Handle session expiry centrally (e.g. a fetch wrapper that redirects on 401) so analyze errors degrade gracefully.","Log res.status and the detail body in dev to distinguish auth vs validation vs server failures quickly."],"tags":["http-error","fetch","api-client","network"],"backgroundTag":"http-non-ok-response","analyzedSha":"116f9cc3b00e1ac91734a6c2679bf41ea64a0edc","analyzedAt":"2026-08-28T22:51:40.999Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}