{"record":{"id":"1950357509973412","repo":"srbhr/Resume-Matcher","slug":"resume-content-changed-or-could-not-be-uniquely-ma","errorCode":null,"errorMessage":"Resume content changed or could not be uniquely matched. Please regenerate and try again.","messagePattern":"Resume content changed or could not be uniquely matched\\. Please regenerate and try again\\.","errorType":"http","errorClass":"HTTPException","httpStatus":409,"severity":"error","filePath":"apps/backend/app/routers/enrichment.py","lineNumber":781,"sourceCode":"                if not _lines_equal(additional.get(\"technicalSkills\"), expected_original_content):\n                    apply_failures.append(item_id)\n                    continue\n                additional[\"technicalSkills\"] = new_content\n            elif \"technicalSkills\" in updated_data:\n                # Fallback for legacy data structure\n                if not _lines_equal(updated_data.get(\"technicalSkills\"), expected_original_content):\n                    apply_failures.append(item_id)\n                    continue\n                updated_data[\"technicalSkills\"] = new_content\n            else:\n                apply_failures.append(item_id)\n\n    if apply_failures:\n        logger.warning(\n            \"apply-regenerated: refusing to apply due to mismatched/missing items. \"\n            f\"resume_id={resume_id} item_ids={apply_failures}\"\n        )\n        raise HTTPException(\n            status_code=409,\n            detail=(\n                \"Resume content changed or could not be uniquely matched. \"\n                \"Please regenerate and try again.\"\n            ),\n        )\n\n    # Update the resume in database\n    updated_content = json.dumps(updated_data, indent=2)\n    try:\n        await db.update_resume(\n            resume_id,\n            {\n                \"content\": updated_content,\n                \"processed_data\": updated_data,\n            },\n        )\n    except Exception as e:","sourceCodeStart":763,"sourceCodeEnd":799,"githubUrl":"https://github.com/srbhr/Resume-Matcher/blob/116f9cc3b00e1ac91734a6c2679bf41ea64a0edc/apps/backend/app/routers/enrichment.py#L763-L799","documentation":"A 409 Conflict raised by apply_regenerated_items in the enrichment router. It means one or more item IDs the client sent back for applying AI-regenerated changes no longer uniquely matched the resume's current content (content changed, item deleted, or duplicate content made matching ambiguous). The endpoint deliberately refuses partial application so stale edits never overwrite newer resume state.","triggerScenarios":"POSTing the regenerated-items payload after the resume was edited/regenerated elsewhere; referencing item_ids that no longer exist; sending two items whose original content both match the same resume item (ambiguous duplicate); a concurrent apply request already mutated the items.","commonSituations":"User regenerates in a second tab while the first tab's apply is pending; client caches the regeneration response and applies it after a manual edit; the AI regeneration omitted/renamed item IDs so the client echoes stale or duplicated IDs; race between two apply calls.","solutions":["Regenerate the enrichment items (call the regenerate endpoint again) and immediately apply with the fresh payload","Refetch the resume to confirm its updated_at/content hash matches what the regeneration was based on before applying","Deduplicate item_ids in the request and ensure each original_content uniquely maps to one resume item","Retry after refreshing the UI so the client holds current resume state"],"exampleFix":"// before: applying stale regenerated items\nawait api.post(`/enrichment/${resumeId}/apply-regenerated`, cachedPayload);\n// after: refetch resume and abort if it changed since regeneration\nconst resume = await api.get(`/resumes/${resumeId}`);\nif (resume.contentHash !== cachedPayload.baseContentHash) {\n  throw new StaleResumeError('Resume changed; regenerate before applying');\n}\nawait api.post(`/enrichment/${resumeId}/apply-regenerated`, cachedPayload);","handlingStrategy":"validation","validationCode":"async function canApply(resumeId, payload) {\n  const resume = await api.get(`/resumes/${resumeId}`);\n  if (resume.contentHash !== payload.baseContentHash) return false;\n  const ids = new Set(payload.items.map(i => i.item_id));\n  if (ids.size !== payload.items.length) return false; // duplicates\n  return payload.items.every(i => typeof i.item_id === 'string' && i.item_id.length > 0);\n}","typeGuard":"function isValidRegenPayload(p) {\n  return Array.isArray(p?.items) && p.items.length > 0 &&\n    p.items.every(i => typeof i?.item_id === 'string' && typeof i?.original_content === 'string');\n}","tryCatchPattern":"try {\n  await api.post(`/enrichment/${resumeId}/apply-regenerated`, payload);\n} catch (e) {\n  if (e.response?.status === 409) {\n    await regenerateAndReapply(resumeId); // stale: regenerate fresh items\n  } else throw e;\n}","preventionTips":["Always apply immediately after regenerating; never cache regenerated payloads across user edits","Send a content hash/updated_at from regeneration time and verify it before applying","Deduplicate item_ids client-side to avoid ambiguous matches","Refresh resume state before each apply call"],"tags":["http","conflict","stale-state","fastapi"],"backgroundTag":"resume-content-conflict","analyzedSha":"116f9cc3b00e1ac91734a6c2679bf41ea64a0edc","analyzedAt":"2026-08-28T22:51:40.999Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}