srbhr/Resume-Matcher · error · ValueError
personalInfo fields changed: {', '.join(mismatches)}
Error message
personalInfo fields changed: {', '.join(mismatches)} What it means
The core immutability guarantee of the improve/confirm flow: after validating shape, _validate_confirm_payload unions the key sets of original and improved personalInfo, normalizes each value, and raises this ValueError listing every field whose value changed. It guarantees the tailoring pipeline never alters identity data (name, email, phone, location, links) — only experience/skills content. Surfaced as HTTP 400.
Source
Thrown at apps/backend/app/routers/resumes.py:553
if improved_info is None:
raise ValueError("Improved resume missing personalInfo")
if not isinstance(original_info, dict):
raise ValueError(
f"Original personalInfo is not a dict: {type(original_info).__name__}"
)
if not isinstance(improved_info, dict):
raise ValueError(
f"Improved personalInfo is not a dict: {type(improved_info).__name__}"
)
fields = set(original_info.keys()) | set(improved_info.keys())
mismatches = [
field
for field in sorted(fields)
if _normalize_personal_info_value(original_info.get(field))
!= _normalize_personal_info_value(improved_info.get(field))
]
if mismatches:
raise ValueError(f"personalInfo fields changed: {', '.join(mismatches)}")
async def _generate_auxiliary_messages(
improved_data: dict[str, Any],
job_content: str,
language: str,
enable_cover_letter: bool,
enable_outreach: bool,
enable_interview_prep: bool,
) -> tuple[str | None, str | None, str | None, InterviewPrepData | None, list[str]]:
"""Generate cover letter, outreach, interview prep, and resume title.
Returns (cover_letter, outreach_message, title, interview_prep, warnings).
"""
cover_letter = None
outreach_message = None
title = None
interview_prep = NoneView on GitHub (pinned to 116f9cc3b0)
Solutions
- Copy the original personalInfo values into the improved payload before confirming — identity fields must be byte-identical after normalization
- Re-run preview and submit its improved data unchanged
- Check whether a custom diff allow-list or prompt change is letting the LLM edit personalInfo paths, and block that path
Example fix
// before: pipeline changed the email in improved_data
"personalInfo": {"name": "Jane Doe", "email": "jane@newmail.com"}
// after: identity block identical to the original resume
"personalInfo": {"name": "Jane Doe", "email": "jane@example.com"} Defensive patterns
Strategy: validation
Validate before calling
// client: pre-flight parity check before confirm (mirrors the server's normalization)
function personalInfoChanged(orig, improved) {
const norm = v => (v ?? '').toString().replace(/\s+/g, ' ').trim().toLowerCase();
const keys = new Set([...Object.keys(orig), ...Object.keys(improved)]);
return [...keys].filter(k => norm(orig[k]) !== norm(improved[k]));
}
const mismatches = personalInfoChanged(original.personalInfo, improvedData.personalInfo);
if (mismatches.length) throw new Error(`identity fields edited: ${mismatches}`); Try / catch
try {
await api.post('/resumes/improve/confirm', body);
} catch (e) {
if (e.response?.status === 400 && /personalInfo fields changed/.test(e.response?.data?.detail ?? '')) {
// restore original identity block and resubmit
body.improved_data.personalInfo = structuredClone(original.personalInfo);
await api.post('/resumes/improve/confirm', body);
}
} Prevention
- Make the personalInfo block read-only in the tailoring UI
- Lock/block the personalInfo path in any custom diff allow-list so the LLM cannot edit it
- Always take improved_data from the preview response instead of re-entering it
- Compare identity fields against the original before calling confirm
When it happens
Trigger: POST /resumes/improve/confirm where any personalInfo field differs between the stored original and the submitted improved data — normalization differences like whitespace, case, or dash/date variants are folded away, so this fires only on real value changes (different email, missing phone, altered LinkedIn URL).
Common situations: LLM rewrote the identity block despite safety nets; a client (or script) modified personalInfo between preview and confirm; diff application edited personalInfo paths; the preview hash is fine but the payload drifted.
Related errors
- Original resume missing personalInfo
- Improved resume missing personalInfo
- Original personalInfo is not a dict: {type(original_info).__
- Improved personalInfo is not a dict: {type(improved_info).__
- ${message = data.detail or Failed to update LLM config (stat
AI-assisted analysis of srbhr/Resume-Matcher@116f9cc3b0 (2026-08-28).
Data as JSON: /api/errors/f59d44961281a776.
Report an issue: GitHub.