{"record":{"id":"17bcde47865d41ef","repo":"srbhr/Resume-Matcher","slug":"a-master-resume-already-exists-delete-it-before-c","errorCode":null,"errorMessage":"A master resume already exists. Delete it before creating a new one.","messagePattern":"A master resume already exists\\. Delete it before creating a new one\\.","errorType":"http","errorClass":"HTTPException","httpStatus":409,"severity":"error","filePath":"apps/backend/app/routers/resume_wizard.py","lineNumber":77,"sourceCode":"        logger.error(\"Resume wizard turn validation failed: %s\", e)\n        raise HTTPException(status_code=422, detail=\"Could not update the resume draft.\")\n    except Exception as e:\n        logger.error(\"Resume wizard turn failed: %s\", e)\n        raise HTTPException(\n            status_code=500,\n            detail=\"Resume wizard failed. Please try again.\",\n        )\n\n\n@router.post(\"/finalize\", response_model=ResumeWizardFinalizeResponse)\nasync def finalize_resume_wizard(\n    request: ResumeWizardFinalizeRequest,\n) -> ResumeWizardFinalizeResponse:\n    \"\"\"Create the master resume from a validated wizard draft.\"\"\"\n    try:\n        current_master = await db.get_master_resume()\n        if current_master and current_master.get(\"processing_status\") == \"ready\":\n            raise HTTPException(\n                status_code=409,\n                detail=\"A master resume already exists. Delete it before creating a new one.\",\n            )\n\n        normalized = normalize_resume_data(\n            request.state.resume_data.model_dump(mode=\"json\")\n        )\n        data = ResumeData.model_validate(normalized).model_dump(mode=\"json\")\n        content = json.dumps(data, ensure_ascii=False, sort_keys=True)\n        name = data.get(\"personalInfo\", {}).get(\"name\", \"\").strip() or \"Resume\"\n        title = f\"{name} Master Resume\"\n        # Set the title in the atomic create so a separate update can't fail and\n        # leave a committed-but-untitled master behind (which would 409 on retry).\n        resume = await db.create_resume_atomic_master(\n            content=content,\n            content_type=\"json\",\n            filename=f\"AI Resume Wizard - {name}.json\",\n            processed_data=data,","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/srbhr/Resume-Matcher/blob/116f9cc3b00e1ac91734a6c2679bf41ea64a0edc/apps/backend/app/routers/resume_wizard.py#L59-L95","documentation":"A 409 raised at the start of finalize_resume_wizard when db.get_master_resume() returns an existing resume already marked processing_status 'ready'. Only one master resume may exist; finalize is refused until it is deleted.","triggerScenarios":"Calling POST /resume-wizard/finalize when a ready master resume already exists; concurrent finalize calls after one succeeded; environment seeded with a master resume from a previous session.","commonSituations":"User finishes the wizard a second time without deleting the first master resume; two browser tabs finalize simultaneously; test/dev database reused across runs without cleanup.","solutions":["Delete the existing master resume via the resume deletion endpoint, then re-run finalize","Fetch the master resume first and skip the wizard if one already exists (redirect user to it)","For tests/dev, clear the master resume in setup/teardown before finalizing","Serialize finalize calls in the client so only one runs at a time"],"exampleFix":"// before\nawait api.post('/resume-wizard/finalize', { state });\n// after\nconst master = await api.get('/resumes/master');\nif (master?.processing_status === 'ready') {\n  showToast('Master resume already exists');\n} else {\n  await api.post('/resume-wizard/finalize', { state });\n}","handlingStrategy":"validation","validationCode":"async function canFinalizeWizard(state) {\n  const master = await api.get('/resumes/master').catch(e => e.response?.status === 404 ? null : Promise.reject(e));\n  if (master && master.processing_status === 'ready') {\n    throw new ConflictError('Master resume already exists — delete it or reuse it');\n  }\n  return true;\n}","typeGuard":"function hasReadyMaster(r) {\n  return r != null && r.processing_status === 'ready';\n}","tryCatchPattern":"try {\n  await api.post('/resume-wizard/finalize', { state });\n} catch (e) {\n  if (e.response?.status === 409) {\n    navigate('/resumes/master'); // existing master wins\n  } else throw e;\n}","preventionTips":["Check for an existing ready master before starting the wizard","Delete the old master resume before re-running finalize","Serialize finalize calls; disable the button after first click","Clean master resumes in test setup/teardown"],"tags":["http","conflict","duplicate-resource","fastapi"],"backgroundTag":"duplicate-resource-conflict","analyzedSha":"116f9cc3b00e1ac91734a6c2679bf41ea64a0edc","analyzedAt":"2026-08-28T22:51:40.999Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}