jamiepine/voicebox · warning · HTTPException

Profile not found or no avatar to delete

Error message

Profile not found or no avatar to delete

What it means

DELETE /profiles/{profile_id}/avatar calls profiles.delete_avatar; if it returns falsy the route raises 404 'Profile not found or no avatar to delete'. The service collapses two distinct conditions into one False return -- the profile_id does not exist, or the profile exists but has no avatar_path to clear. Callers cannot tell which from the response alone.

Source

Thrown at backend/routes/profiles.py:277

    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(
    profile_id: str,
    db: Session = Depends(get_db),
):
    """Export a voice profile as a ZIP archive."""
    try:
        profile = await profiles.get_profile(profile_id, db)
        if not profile:
            raise HTTPException(status_code=404, detail="Profile not found")

        zip_bytes = export_import.export_profile_to_zip(profile_id, db)

        safe_name = "".join(c for c in profile.name if c.isalnum() or c in (" ", "-", "_")).strip()
        if not safe_name:

View on GitHub (pinned to 51f49dea19)

Solutions

  1. Treat the 404 as success for idempotent avatar deletion (end state: no avatar).
  2. If you need to distinguish the two cases, GET /profiles/{profile_id} first and inspect avatar_path.
  3. Guard the UI delete button so it only appears when an avatar is known to exist.
Defensive patterns

Strategy: try-catch

Validate before calling

async def avatar_exists(client, profile_id) -> bool:
    profile = (await client.get(f"/profiles/{profile_id}")).json()
    return bool(profile.get("avatar_path"))

Try / catch

resp = await client.delete(f"/profiles/{profile_id}/avatar")
if resp.status_code == 404:
    # Either profile is gone or there was no avatar -- both are acceptable end states
    pass
else:
    resp.raise_for_status()
refresh_profile(profile_id)

Prevention

When it happens

Trigger: Calling delete on a profile that never had an avatar uploaded; calling delete on a profile_id that was already removed; double-deleting after a successful first delete (the second sees avatar_path already None).

Common situations: Frontend always shows a 'remove avatar' button regardless of whether one is set; user clicks remove twice; sync script re-runs.

Related errors


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