invoke-ai/InvokeAI · error · UnreadableImageError

Unable to decode image {image_path}: {e}

Error message

Unable to decode image {image_path}: {e}

What it means

_regenerate_thumbnail opens the source image with Pillow to rebuild its WEBP thumbnail. If decoding fails with UnidentifiedImageError, DecompressionBombError, or OSError, it wraps the cause into UnreadableImageError('Unable to decode image {image_path}: {e}'). It means the file at image_path is missing, not a valid image, truncated, or too large to decode safely.

Source

Thrown at invokeai/app/services/image_moves/image_moves_default.py:832

                """--sql
                SELECT image_name FROM images
                WHERE image_name > ?
                  AND deleted_at IS NULL
                ORDER BY image_name
                LIMIT 1;
                """,
                (last_image_name,),
            )
            row = cursor.fetchone()
        return None if row is None else cast(str, row[0])

    def _regenerate_thumbnail(self, image_path: Path, thumbnail_path: Path) -> None:
        thumbnail_path.parent.mkdir(parents=True, exist_ok=True)
        try:
            with Image.open(image_path) as image:
                thumbnail = make_thumbnail(image)
        except (UnidentifiedImageError, Image.DecompressionBombError, OSError) as e:
            raise UnreadableImageError(f"Unable to decode image {image_path}: {e}") from e
        with tempfile.NamedTemporaryFile(
            dir=thumbnail_path.parent, prefix=f".{thumbnail_path.name}.", suffix=".tmp", delete=False
        ) as temp_file:
            temp_path = Path(temp_file.name)
        try:
            thumbnail.save(temp_path, format="WEBP")
            self._fsync_file(temp_path)
            os.replace(temp_path, thumbnail_path)
            self._fsync_file(thumbnail_path)
            self._fsync_dir(thumbnail_path.parent)
        finally:
            temp_path.unlink(missing_ok=True)

    def _mark_missing_intermediate_moved(
        self,
        job_id: int,
        image_name: str,
        old_path: Path,

View on GitHub (pinned to 0b6a024f2f)

Solutions

  1. Try re-opening and re-saving the image (or re-generate it in InvokeAI) to repair or replace the corrupt file.
  2. Check the file: verify it opens with PIL externally; if it's not a real image, remove or replace it.
  3. For decompression-bomb errors, raise PIL.Image.MAX_IMAGE_PIXELS if the image is trusted, then retry the move.
  4. Check file permissions (chmod/chown) if the cause is an OSError from access denial.
  5. Mark/allow the item to proceed without a thumbnail if the library supports skipping thumbnail regeneration.

Example fix

// before: corrupt file aborts the move
# 0-byte images/x/abc.png present, thumbnail regen fails
// after: repair or remove corrupt file first
from PIL import Image
Image.open("images/x/abc.png").verify()  # diagnose
# if unrecoverable: replace the file, then re-run recovery
Defensive patterns

Strategy: try-catch

Validate before calling

from pathlib import Path
from PIL import Image, UnidentifiedImageError
def image_is_decodable(path: Path) -> bool:
    if not path.is_file() or path.stat().st_size == 0:
        return False
    try:
        with Image.open(path) as im:
            im.verify()
        return True
    except (UnidentifiedImageError, Image.DecompressionBombError, OSError):
        return False

Type guard

def is_readable_image_file(path: Path) -> bool:
    return path.is_file() and path.stat().st_size > 0 and path.suffix.lower() in {'.png', '.jpg', '.jpeg', '.webp'}

Try / catch

from invokeai.app.services.image_moves.image_moves_default import UnreadableImageError
try:
    service.complete_partial_filesystem_moves(job_id)
except UnreadableImageError as e:
    # e names the undecodable path; quarantine/replace it, then re-run

Prevention

When it happens

Trigger: Completing a filesystem move where the thumbnail must be regenerated: source file is corrupt/truncated (interrupted download/save), is not a real image (renamed file, 0-byte placeholder), exceeds Pillow's decompression-bomb pixel limit, or has permissions preventing read.

Common situations: Interrupted image downloads; corrupted files on failing disks; SVG/text files with image extensions; very large panorama images exceeding PIL.Image.MAX_IMAGE_PIXELS; partially restored backups.

Understand the failure class

Related errors


AI-assisted analysis of invoke-ai/InvokeAI@0b6a024f2f (2026-08-29). Data as JSON: /api/errors/1000b01345f11ded. Report an issue: GitHub.