{"record":{"id":"e2d23797eace002b","repo":"srbhr/Resume-Matcher","slug":"cover-letter-can-only-be-generated-for-tailored-re","errorCode":null,"errorMessage":"Cover letter can only be generated for tailored resumes. Please tailor this resume to a job description first.","messagePattern":"Cover letter can only be generated for tailored resumes\\. Please tailor this resume to a job description first\\.","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"apps/backend/app/routers/resumes.py","lineNumber":1786,"sourceCode":"@router.post(\n    \"/{resume_id}/generate-cover-letter\", response_model=GenerateContentResponse\n)\nasync def generate_cover_letter_endpoint(resume_id: str) -> GenerateContentResponse:\n    \"\"\"Generate a cover letter on-demand for an existing tailored resume.\n\n    This endpoint allows users to generate a cover letter after a resume has been\n    tailored, without needing to re-tailor the entire resume. It requires:\n    - The resume must be a tailored resume (has parent_id)\n    - The resume must have an associated job context in the improvements table\n    \"\"\"\n    # Get the resume\n    resume = await db.get_resume(resume_id)\n    if not resume:\n        raise HTTPException(status_code=404, detail=\"Resume not found\")\n\n    # Check if it's a tailored resume (has parent_id)\n    if not resume.get(\"parent_id\"):\n        raise HTTPException(\n            status_code=400,\n            detail=\"Cover letter can only be generated for tailored resumes. \"\n            \"Please tailor this resume to a job description first.\",\n        )\n\n    # Get improvement record to find the job_id\n    improvement = await db.get_improvement_by_tailored_resume(resume_id)\n    if not improvement:\n        raise HTTPException(\n            status_code=400,\n            detail=\"No job context found for this resume. \"\n            \"The resume may have been created before job tracking was implemented.\",\n        )\n\n    # Get the job description\n    job = await db.get_job(improvement[\"job_id\"])\n    if not job:\n        raise HTTPException(","sourceCodeStart":1768,"sourceCodeEnd":1804,"githubUrl":"https://github.com/srbhr/Resume-Matcher/blob/116f9cc3b00e1ac91734a6c2679bf41ea64a0edc/apps/backend/app/routers/resumes.py#L1768-L1804","documentation":"HTTP 400 raised by generate_cover_letter_endpoint when the resume exists but has no parent_id, meaning it is an original (untailored) resume. Cover letters are generated only from tailored resumes, since the tailored resume carries the job-specific context needed for the letter.","triggerScenarios":"Requesting cover-letter generation for a base/original resume (created by upload, not by the tailor-to-job flow), i.e. resume.parent_id is null or missing.","commonSituations":"User clicks 'Generate cover letter' on an uploaded resume instead of a tailored variant; UI list mixes base and tailored resumes without labeling; older records created before the parent_id linkage was added.","solutions":["First tailor the resume to a job description (run the tailor flow) to create a child resume with parent_id, then generate the cover letter from that tailored resume.","Check resume.parent_id client-side and only enable cover-letter generation for tailored resumes.","Label base vs tailored resumes distinctly in the UI to prevent misclicks.","If a legacy resume should be tailored, re-run tailoring to create the linked child record."],"exampleFix":"// before\nawait api.post(`/resumes/${resumeId}/generate-cover-letter`);\n\n// after\nconst resume = await api.get(`/resumes/${resumeId}`);\nif (!resume.data.parent_id) {\n  const tailored = await api.post(`/resumes/${resumeId}/tailor`, { jobId });\n  resumeId = tailored.data.id;\n}\nawait api.post(`/resumes/${resumeId}/generate-cover-letter`);","handlingStrategy":"validation","validationCode":"const resume = await api.get(`/resumes/${resumeId}`);\nif (!resume.data.parent_id) {\n  throw new Error('Not a tailored resume — run the tailor flow first.');\n}","typeGuard":"const isTailored = (r) => Boolean(r && r.parent_id);","tryCatchPattern":"try {\n  await api.post(`/resumes/${resumeId}/generate-cover-letter`);\n} catch (e) {\n  if (e.response?.status === 400 && /tailored resumes/.test(e.response.data?.detail ?? '')) {\n    notify('Please tailor this resume to a job first.');\n    openTailorDialog(resumeId);\n  } else throw e;\n}","preventionTips":["Only enable cover-letter buttons for resumes with parent_id set.","Visually distinguish base resumes from tailored resumes in lists.","Route users into the tailor flow when they attempt generation on a base resume."],"tags":["http-400","business-rule","resume-tailoring"],"backgroundTag":"operation-not-allowed-for-entity-type","analyzedSha":"116f9cc3b00e1ac91734a6c2679bf41ea64a0edc","analyzedAt":"2026-08-28T22:51:40.999Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}