{"record":{"id":"6ffaf7582b9cd5a4","repo":"jamiepine/voicebox","slug":"avatar-file-not-found","errorCode":null,"errorMessage":"Avatar file not found","messagePattern":"Avatar file not found","errorType":"http","errorClass":"HTTPException","httpStatus":404,"severity":"error","filePath":"backend/routes/profiles.py","lineNumber":264,"sourceCode":"        Path(tmp_path).unlink(missing_ok=True)\n\n\n@router.get(\"/profiles/{profile_id}/avatar\")\nasync def get_profile_avatar(\n    profile_id: str,\n    db: Session = Depends(get_db),\n):\n    \"\"\"Get avatar image for a profile.\"\"\"\n    profile = await profiles.get_profile(profile_id, db)\n    if not profile:\n        raise HTTPException(status_code=404, detail=\"Profile not found\")\n\n    if not profile.avatar_path:\n        raise HTTPException(status_code=404, detail=\"No avatar found for this profile\")\n\n    avatar_path = config.resolve_storage_path(profile.avatar_path)\n    if avatar_path is None or not avatar_path.exists():\n        raise HTTPException(status_code=404, detail=\"Avatar file not found\")\n\n    return FileResponse(avatar_path)\n\n\n@router.delete(\"/profiles/{profile_id}/avatar\")\nasync def delete_profile_avatar(\n    profile_id: str,\n    db: Session = Depends(get_db),\n):\n    \"\"\"Delete avatar image for a profile.\"\"\"\n    success = await profiles.delete_avatar(profile_id, db)\n    if not success:\n        raise HTTPException(status_code=404, detail=\"Profile not found or no avatar to delete\")\n    return {\"message\": \"Avatar deleted successfully\"}\n\n\n@router.get(\"/profiles/{profile_id}/export\")\nasync def export_profile(","sourceCodeStart":246,"sourceCodeEnd":282,"githubUrl":"https://github.com/jamiepine/voicebox/blob/51f49dea198384b4eb6087b72c17057c6eb1c1cd/backend/routes/profiles.py#L246-L282","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Verify the storage root: print config.resolve_storage_path(profile.avatar_path) server-side and check it points where you expect.","Restore the missing blob from backup, or clear avatar_path in the DB so the profile reports 'No avatar found' instead.","Mount a persistent volume at the configured storage path and redeploy.","Audit the storage dir against the avatar_path column to find other drifted rows."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"# Reconcile storage before relying on the avatar endpoint\nimport requests\n\ndef storage_path_resolves(profile_avatar_path, storage_root):\n    candidate = (storage_root / profile_avatar_path) if profile_avatar_path else None\n    return candidate is not None and candidate.exists()","typeGuard":null,"tryCatchPattern":"resp = await client.get(f\"/profiles/{profile_id}/avatar\")\nif resp.status_code == 404:\n    detail = resp.json()[\"detail\"]\n    if detail == \"Avatar file not found\":\n        alert_admin(f\"storage drift on profile {profile_id}\")\n        # fall back to default and clear the stale avatar_path server-side\n    use_default_avatar()\nelse:\n    resp.raise_for_status()","preventionTips":["Mount persistent storage at the configured storage root in every environment.","Run a periodic reconciliation of avatar_path rows against the blob directory.","When restoring DB from backup, restore the blob dir in the same step."],"tags":["fastapi","avatar","storage","config-drift","not-found"],"backgroundTag":null,"analyzedSha":"51f49dea198384b4eb6087b72c17057c6eb1c1cd","analyzedAt":"2026-08-12T16:51:42.824Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}