srbhr/Resume-Matcher · error · HTTPException
Preview required before confirmation. Please retry preview.
Error message
Preview required before confirmation. Please retry preview.
What it means
improve_resume_confirm_endpoint raises HTTP 400 when the job record has no preview_hashes (or the stored preview_hashes dict yields an empty allowed set), meaning no preview was ever run — or its hashes were never persisted — for this resume/job pair before confirmation was attempted.
Source
Thrown at apps/backend/app/routers/resumes.py:1171
preview_hashes = job.get("preview_hashes")
allowed_hashes: set[str] = set()
if isinstance(preview_hashes, dict):
allowed_hashes.update(preview_hashes.values())
elif isinstance(preview_hashes, list):
allowed_hashes.update(
[value for value in preview_hashes if isinstance(value, str)]
)
else:
preview_hash = job.get("preview_hash")
if isinstance(preview_hash, str):
allowed_hashes.add(preview_hash)
if not allowed_hashes:
logger.warning(
"Rejecting confirm; preview hash missing for job %s.",
request.job_id,
)
raise HTTPException(
status_code=400,
detail="Preview required before confirmation. Please retry preview.",
)
request_hash = _hash_improved_data(improved_data)
if request_hash not in allowed_hashes:
logger.warning("Resume confirm rejected due to preview hash mismatch.")
raise HTTPException(
status_code=400,
detail="Invalid improved resume data. Please retry preview.",
)
stage = "calculate_diff"
response_warnings: list[str] = []
diff_summary, detailed_changes, diff_error = _calculate_diff_from_resume(
resume,
improved_data,
)View on GitHub (pinned to 116f9cc3b0)
Solutions
- Run the preview endpoint first and wait for a successful response, then confirm immediately with the same resume_id/job_id pair
- If preview previously timed out, raise REQUEST_TIMEOUT_SECONDS / NEXT_PUBLIC_REQUEST_TIMEOUT_MS and redo preview before confirming
- After a DB reset or backend redeploy, restart the whole preview→confirm flow
- Ensure the confirm request uses the same job_id that the successful preview used (hashes are stored per job)
Example fix
// before
confirm({resume_id, job_id}); // never previewed
// after
const preview = await api.preview({resume_id, job_id}); // succeeds and stores hashes
await api.confirm({resume_id, job_id, improved_data: preview.improved}); Defensive patterns
Strategy: validation
Validate before calling
async function canConfirm(resumeId: string, jobId: string): Promise<boolean> {
const job = await fetch(`/api/jobs/${jobId}`).then(r => r.json()).catch(() => null);
return !!job?.preview_hashes && Object.keys(job.preview_hashes).length > 0;
}
if (!(await canConfirm(resumeId, jobId))) await api.improvePreview({resume_id, job_id: jobId}); Type guard
function hasPreviewHashes(job: unknown): job is {preview_hashes: Record<string, string>} {
return typeof job === 'object' && job !== null &&
typeof (job as any).preview_hashes === 'object' && (job as any).preview_hashes !== null &&
Object.keys((job as any).preview_hashes).length > 0;
} Try / catch
try {
await api.improveConfirm({resume_id, job_id, improved_data});
} catch (e) {
if (e.response?.status === 400 && /Preview required/.test(e.response.data?.detail ?? '')) {
const preview = await api.improvePreview({resume_id, job_id});
await api.improveConfirm({resume_id, job_id, improved_data: preview.improved});
} else throw e;
} Prevention
- Only call confirm after a fully successful preview response — never after a timeout or failure
- If preview timed out, fix the timeout config and rerun preview before confirming
- Treat preview→confirm as one state machine; reset the flow after DB resets or redeploys
- Use the same job_id for preview and confirm (hashes are persisted per job)
When it happens
Trigger: POST to improve/confirm where job.preview_hashes is missing, not a dict, or empty: confirming without ever calling preview, after a DB reset that wiped job metadata, or after preview failed/timed out so hashes were never stored.
Common situations: User bookmarks the confirm step and replays it later; preview timed out (error 124/125) but the client still tries confirm; backend redeploy wiped in-memory/DB preview state; preview was run for a different job_id.
Related errors
- Invalid improved resume data. Please retry preview.
- Cover letter can only be generated for tailored resumes. Ple
- Unsupported UI language: {request.ui_language}. Supported: {
- Unsupported content language: {request.content_language}. Su
- Unsupported prompt id: {request.default_prompt_id}. Supporte
AI-assisted analysis of srbhr/Resume-Matcher@116f9cc3b0 (2026-08-28).
Data as JSON: /api/errors/71bf15126a944bac.
Report an issue: GitHub.