odysseus-dev/odysseus · error · HTTPException

Image file not found

Error message

Image file not found

What it means

HTTP 404 raised by POST /api/gallery/{image_id}/rotate when the DB row exists but the backing file is missing on disk (img_path.exists() is false). This indicates DB/filesystem desync: the record survived while the file under GALLERY_IMAGE_DIR was deleted, moved, or never written.

Source

Thrown at routes/gallery/gallery_routes.py:530

        try:
            angle = int(data.get("angle", 90))
        except (TypeError, ValueError):
            raise HTTPException(400, "Invalid angle")
        if angle not in (90, -90, 180, 270):
            raise HTTPException(400, "Angle must be 90, -90, 180, or 270")

        user = get_current_user(request)
        db = SessionLocal()
        try:
            img = db.query(GalleryImage).filter(GalleryImage.id == image_id).first()
            if not img:
                raise HTTPException(404, "Image not found")
            if not user or img.owner != user:
                raise HTTPException(403, "Not your image")

            img_path = _gallery_image_path(img.filename)
            if not img_path.exists():
                raise HTTPException(404, "Image file not found")

            # PIL rotates counter-clockwise; the API takes "clockwise"
            # convention so we negate to match user expectation.
            with Image.open(img_path) as pil:
                rotated = pil.rotate(-angle, expand=True)
                # Recompute hash so dedupe stays accurate.
                buf = BytesIO()
                ext = img.filename.rsplit(".", 1)[-1].lower()
                save_kwargs = {}
                if ext in ("jpg", "jpeg"):
                    save_kwargs["quality"] = 95
                    fmt = "JPEG"
                elif ext == "webp":
                    fmt = "WEBP"
                    save_kwargs["quality"] = 95
                else:
                    fmt = "PNG"
                rotated.save(buf, format=fmt, **save_kwargs)

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Check GALLERY_IMAGE_DIR for the expected filename from img.filename.
  2. Restore the file from backup, or delete the orphan DB row so the gallery stays consistent.
  3. Mount the gallery directory on persistent storage in containerized deployments.
Defensive patterns

Strategy: try-catch

Try / catch

try { await rotateImage(id, angle); } catch (e) {
  if (e.status === 404) {
    const meta = await getImage(id);
    if (meta) reportDesync(id, 'DB row exists but file missing'); // ops alert
  }
}

Prevention

When it happens

Trigger: Manual cleanup of the gallery image directory; a crashed write in a previous operation; volume/container restarts losing ephemeral storage; the filename column containing a stale path.

Common situations: Docker deployments without persistent volumes; rsync/cron jobs pruning 'orphan' files; partial restore from backup that recovered the DB but not the files.

Related errors


AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14). Data as JSON: /api/errors/62630122458e5922. Report an issue: GitHub.