srbhr/Resume-Matcher · error · ValueError
Resume not found: {resume_id}
Error message
Resume not found: {resume_id} What it means
update_resume in the backend database layer fetches a Resume row by primary key via session.get(Resume, resume_id) and raises ValueError when no row exists. It is the guard used by many write paths (upload, enhancements, cover letter updates, retries) to prevent silently updating a nonexistent record.
Source
Thrown at apps/backend/app/database.py:318
async def get_master_resume(self) -> dict[str, Any] | None:
"""Get the master resume if exists."""
async with self._session() as session:
result = await session.execute(
select(Resume).where(Resume.is_master.is_(True))
)
row = result.scalars().first()
return self._resume_to_dict(row) if row else None
async def update_resume(self, resume_id: str, updates: dict[str, Any]) -> dict[str, Any]:
"""Update resume by ID.
Raises:
ValueError: If resume not found.
"""
async with self._session() as session:
row = await session.get(Resume, resume_id)
if row is None:
raise ValueError(f"Resume not found: {resume_id}")
for key, value in updates.items():
if hasattr(row, key):
setattr(row, key, value)
else:
logger.warning("Ignoring unknown resume field on update: %s", key)
row.updated_at = _now()
await session.commit()
return self._resume_to_dict(row)
async def delete_resume(self, resume_id: str) -> bool:
"""Delete resume by ID."""
async with self._session() as session:
row = await session.get(Resume, resume_id)
if row is None:
return False
await session.delete(row)
await session.commit()
return TrueView on GitHub (pinned to 116f9cc3b0)
Solutions
- Verify the resume_id exists (SELECT or GET endpoint) before calling update_resume
- Return 404 to the client when this ValueError propagates instead of a 500
- Confirm the caller isn't passing a stale ID cached after deletion — refetch the resume list
- Check that ID types match (UUID vs str) so session.get isn't silently missing the row
Example fix
// before await update_resume(session, resume_id, updates); // after const row = await get_resume(session, resume_id); if (!row) return notFoundResponse(resume_id); await update_resume(session, resume_id, updates);
Defensive patterns
Strategy: validation
Validate before calling
async def resume_exists(session, resume_id) -> bool:
return await session.get(Resume, resume_id) is not None
# before updating:
if not await resume_exists(session, resume_id):
raise HTTPException(status_code=404, detail=f"Resume not found: {resume_id}") Try / catch
try:
await update_resume(session, resume_id, updates)
except ValueError as e:
if str(e).startswith("Resume not found"):
raise HTTPException(status_code=404, detail=str(e)) from e
raise Prevention
- Always fetch-and-check the resume before mutating it
- Map this ValueError to HTTP 404 in the API layer
- Invalidate client-side caches when a resume is deleted
- Normalize ID types (str vs UUID) at API boundaries
When it happens
Trigger: Any caller of update_resume (apply_enhancements, upload_resume, update_resume_endpoint, retry_processing, update_cover_letter, apply_regenerated_items) passes a resume_id that has no matching row in the resumes table.
Common situations: Client holds a stale ID after the resume was deleted; ID passed with wrong type/format (e.g. string vs UUID mismatch in lookup); race between delete and update; user-supplied ID in an API call to a resource owned by another user.
Understand the failure class
Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.
Related errors
- ${data.detail || Failed to reset database (status ${res.stat
- LLM completion failed. Please check your API configuration a
- JSON extraction exceeded max recursion depth: {_depth}
- Content too large for JSON extraction: {len(content)} bytes
- No JSON found in response: {original[:200]}
AI-assisted analysis of srbhr/Resume-Matcher@116f9cc3b0 (2026-08-28).
Data as JSON: /api/errors/913ef46aed7a1751.
Report an issue: GitHub.