{"record":{"id":"e077ebc1d379b9bd","repo":"srbhr/Resume-Matcher","slug":"outreach-message-can-only-be-generated-for-tailore","errorCode":null,"errorMessage":"Outreach message can only be generated for tailored resumes. Please tailor this resume to a job description first.","messagePattern":"Outreach message 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":1857,"sourceCode":"\n\n@router.post(\"/{resume_id}/generate-outreach\", response_model=GenerateContentResponse)\nasync def generate_outreach_endpoint(resume_id: str) -> GenerateContentResponse:\n    \"\"\"Generate an outreach message on-demand for an existing tailored resume.\n\n    This endpoint allows users to generate a cold outreach message after a resume\n    has been tailored. 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=\"Outreach message 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":1839,"sourceCodeEnd":1875,"githubUrl":"https://github.com/srbhr/Resume-Matcher/blob/116f9cc3b00e1ac91734a6c2679bf41ea64a0edc/apps/backend/app/routers/resumes.py#L1839-L1875","documentation":"400 raised when the resume exists but has no parent_id, i.e. it is an original uploaded resume rather than a resume tailored to a job. Outreach generation requires a tailored resume because the outreach content derives from the job context captured during tailoring.","triggerScenarios":"POSTing the outreach generation route with the ID of a base/original resume (parent_id absent or null) instead of a tailored copy created from a job description.","commonSituations":"UI passing the wrong node of a resume tree, calling outreach before ever running the tailor step, or legacy resumes that predate the parent_id linking.","solutions":["Tailor the resume to a job description first, then generate outreach for the tailored copy (the resume with parent_id set)","Ensure the client sends the tailored resume ID, not the original resume ID","Check resume.parent_id in the database if unsure which record to use"],"exampleFix":"// before\nawait generateOutreach(originalResume.id);\n// after\nconst tailored = await tailorResume(originalResume.id, jobId);\nawait generateOutreach(tailored.id);","handlingStrategy":"validation","validationCode":"const resume = await getResume(resumeId);\nif (!resume?.parentId) {\n  throw new Error('Resume is not tailored; tailor to a job first');\n}\nawait api.generateOutreach(resumeId);","typeGuard":"function isTailored(r: Resume | null | undefined): r is Resume & { parentId: string } {\n  return !!r && typeof r.parentId === 'string' && r.parentId.length > 0;\n}","tryCatchPattern":"try {\n  await api.generateOutreach(resumeId);\n} catch (e) {\n  if (e.response?.status === 400 && /tailored/i.test(e.response.data?.detail ?? '')) {\n    return api.tailorResume(originalResumeId, jobId).then(t => api.generateOutreach(t.id));\n  }\n  throw e;\n}","preventionTips":["Always tailor before generating outreach; chain the calls in the UI","Track the tailored-resume ID separately from the original resume ID","Check parent_id presence before enabling the outreach action in the UI","Write an integration test covering the base-vs-tailored resume flow"],"tags":["http-400","business-rule","workflow"],"backgroundTag":"missing-tailored-resume","analyzedSha":"116f9cc3b00e1ac91734a6c2679bf41ea64a0edc","analyzedAt":"2026-08-28T22:51:40.999Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}