srbhr/Resume-Matcher · error · HTTPException

Resume content changed or could not be uniquely matched. Ple

Error message

Resume content changed or could not be uniquely matched. Please regenerate and try again.

What it means

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.

Source

Thrown at apps/backend/app/routers/enrichment.py:781

                if not _lines_equal(additional.get("technicalSkills"), expected_original_content):
                    apply_failures.append(item_id)
                    continue
                additional["technicalSkills"] = new_content
            elif "technicalSkills" in updated_data:
                # Fallback for legacy data structure
                if not _lines_equal(updated_data.get("technicalSkills"), expected_original_content):
                    apply_failures.append(item_id)
                    continue
                updated_data["technicalSkills"] = new_content
            else:
                apply_failures.append(item_id)

    if apply_failures:
        logger.warning(
            "apply-regenerated: refusing to apply due to mismatched/missing items. "
            f"resume_id={resume_id} item_ids={apply_failures}"
        )
        raise HTTPException(
            status_code=409,
            detail=(
                "Resume content changed or could not be uniquely matched. "
                "Please regenerate and try again."
            ),
        )

    # Update the resume in database
    updated_content = json.dumps(updated_data, indent=2)
    try:
        await db.update_resume(
            resume_id,
            {
                "content": updated_content,
                "processed_data": updated_data,
            },
        )
    except Exception as e:

View on GitHub (pinned to 116f9cc3b0)

Solutions

  1. Regenerate the enrichment items (call the regenerate endpoint again) and immediately apply with the fresh payload
  2. Refetch the resume to confirm its updated_at/content hash matches what the regeneration was based on before applying
  3. Deduplicate item_ids in the request and ensure each original_content uniquely maps to one resume item
  4. Retry after refreshing the UI so the client holds current resume state

Example fix

// before: applying stale regenerated items
await api.post(`/enrichment/${resumeId}/apply-regenerated`, cachedPayload);
// after: refetch resume and abort if it changed since regeneration
const resume = await api.get(`/resumes/${resumeId}`);
if (resume.contentHash !== cachedPayload.baseContentHash) {
  throw new StaleResumeError('Resume changed; regenerate before applying');
}
await api.post(`/enrichment/${resumeId}/apply-regenerated`, cachedPayload);
Defensive patterns

Strategy: validation

Validate before calling

async function canApply(resumeId, payload) {
  const resume = await api.get(`/resumes/${resumeId}`);
  if (resume.contentHash !== payload.baseContentHash) return false;
  const ids = new Set(payload.items.map(i => i.item_id));
  if (ids.size !== payload.items.length) return false; // duplicates
  return payload.items.every(i => typeof i.item_id === 'string' && i.item_id.length > 0);
}

Type guard

function isValidRegenPayload(p) {
  return Array.isArray(p?.items) && p.items.length > 0 &&
    p.items.every(i => typeof i?.item_id === 'string' && typeof i?.original_content === 'string');
}

Try / catch

try {
  await api.post(`/enrichment/${resumeId}/apply-regenerated`, payload);
} catch (e) {
  if (e.response?.status === 409) {
    await regenerateAndReapply(resumeId); // stale: regenerate fresh items
  } else throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of srbhr/Resume-Matcher@116f9cc3b0 (2026-08-28). Data as JSON: /api/errors/1950357509973412. Report an issue: GitHub.