jamiepine/voicebox · error · HTTPException

Avatar file not found

Error message

Avatar file not found

What it means

GET /profiles/{profile_id}/avatar reaches this branch when profile.avatar_path is set, but config.resolve_storage_path returns None (storage root misconfigured/unresolvable) or the resolved Path does not exist on disk. The route raises 404 'Avatar file not found'. This indicates storage drift: the DB row references a blob that is no longer present, or the storage configuration itself is broken so the path cannot be resolved at all.

Source

Thrown at backend/routes/profiles.py:264

        Path(tmp_path).unlink(missing_ok=True)


@router.get("/profiles/{profile_id}/avatar")
async def get_profile_avatar(
    profile_id: str,
    db: Session = Depends(get_db),
):
    """Get avatar image for a profile."""
    profile = await profiles.get_profile(profile_id, db)
    if not profile:
        raise HTTPException(status_code=404, detail="Profile not found")

    if not profile.avatar_path:
        raise HTTPException(status_code=404, detail="No avatar found for this profile")

    avatar_path = config.resolve_storage_path(profile.avatar_path)
    if avatar_path is None or not avatar_path.exists():
        raise HTTPException(status_code=404, detail="Avatar file not found")

    return FileResponse(avatar_path)


@router.delete("/profiles/{profile_id}/avatar")
async def delete_profile_avatar(
    profile_id: str,
    db: Session = Depends(get_db),
):
    """Delete avatar image for a profile."""
    success = await profiles.delete_avatar(profile_id, db)
    if not success:
        raise HTTPException(status_code=404, detail="Profile not found or no avatar to delete")
    return {"message": "Avatar deleted successfully"}


@router.get("/profiles/{profile_id}/export")
async def export_profile(

View on GitHub (pinned to 51f49dea19)

Solutions

  1. Verify the storage root: print config.resolve_storage_path(profile.avatar_path) server-side and check it points where you expect.
  2. Restore the missing blob from backup, or clear avatar_path in the DB so the profile reports 'No avatar found' instead.
  3. Mount a persistent volume at the configured storage path and redeploy.
  4. Audit the storage dir against the avatar_path column to find other drifted rows.
Defensive patterns

Strategy: try-catch

Validate before calling

# Reconcile storage before relying on the avatar endpoint
import requests

def storage_path_resolves(profile_avatar_path, storage_root):
    candidate = (storage_root / profile_avatar_path) if profile_avatar_path else None
    return candidate is not None and candidate.exists()

Try / catch

resp = await client.get(f"/profiles/{profile_id}/avatar")
if resp.status_code == 404:
    detail = resp.json()["detail"]
    if detail == "Avatar file not found":
        alert_admin(f"storage drift on profile {profile_id}")
        # fall back to default and clear the stale avatar_path server-side
    use_default_avatar()
else:
    resp.raise_for_status()

Prevention

When it happens

Trigger: STORAGE_DIR / VOICEBOX_STORAGE env var changed since the avatar was uploaded; files manually deleted from the storage volume; container restarted with ephemeral storage so uploaded blobs are gone; backup restored the DB without restoring the blob directory.

Common situations: Docker deployment without a persistent volume mounted at the storage path; migration to a new host that copied the DB but not the media dir; cron cleanup that aggressively removed 'old' files.

Related errors


AI-assisted analysis of jamiepine/voicebox@51f49dea19 (2026-08-12). Data as JSON: /api/errors/6ffaf7582b9cd5a4. Report an issue: GitHub.