unslothai/unsloth · error · HTTPException

Not an image file. Allowed: {exts}

Error message

Not an image file. Allowed: {exts}

What it means

HTTP 400 from _safe_dataset_image_path: the filename's extension (lowercased) is not in _DIFFUSION_DATASET_IMAGE_EXTS = {'.png','.jpg','.jpeg','.webp','.bmp'}. The name passed the traversal checks but is not an image by extension — the dataset image routes only serve those five types.

Source

Thrown at studio/backend/routes/training.py:3676

    if width > _MAX_TRAINING_IMAGE_SIDE or height > _MAX_TRAINING_IMAGE_SIDE:
        raise HTTPException(
            status_code = 400,
            detail = (
                f"Image '{original_name}' is too large ({width}x{height}); maximum is "
                f"{_MAX_TRAINING_IMAGE_SIDE}px per side."
            ),
        )


def _safe_dataset_image_path(folder: Path, filename: str) -> Path:
    """Resolve ``filename`` to an image path strictly inside ``folder``. Rejects any path
    separators / traversal / null bytes and non-image extensions."""
    raw = filename or ""
    if "/" in raw or "\\" in raw or ".." in raw or "\x00" in raw or raw != Path(raw).name:
        raise HTTPException(status_code = 400, detail = "Invalid image filename.")
    if Path(raw).suffix.lower() not in _DIFFUSION_DATASET_IMAGE_EXTS:
        exts = ", ".join(sorted(_DIFFUSION_DATASET_IMAGE_EXTS))
        raise HTTPException(status_code = 400, detail = f"Not an image file. Allowed: {exts}")
    path = folder / raw
    # Defense in depth: the real path must stay under the dataset folder.
    try:
        path.resolve().relative_to(folder.resolve())
    except ValueError:
        raise HTTPException(status_code = 400, detail = "Invalid image filename.")
    return path


def _load_metadata_captions(folder: Path) -> dict[str, str]:
    """Read metadata.jsonl / captions.jsonl into {file_name: caption}, mirroring the
    trainer's discovery (keys file_name/video/image/file; caption in the ``text`` column)."""
    import json

    out: dict[str, str] = {}
    for meta_name in ("metadata.jsonl", "captions.jsonl"):
        meta_path = folder / meta_name
        if not meta_path.is_file():

View on GitHub (pinned to 203007d190)

Solutions

  1. Use an allowed extension: convert the file (e.g. magick in.gif out.png) before requesting/uploading it.
  2. Fetch captions via the dataset listing endpoints, not the image route; clips go through the clip endpoints.
  3. When uploading, rename or convert non-supported types instead of forcing them through.

Example fix

# convert a non-allowed type into the accepted set
magick input.gif output.png
Defensive patterns

Strategy: validation

Validate before calling

from pathlib import Path

ALLOWED = {'.png', '.jpg', '.jpeg', '.webp', '.bmp'}

def is_allowed_image_name(name: str) -> bool:
    return Path(name).suffix.lower() in ALLOWED

Type guard

def is_extension_error(exc: HTTPException) -> bool:
    return exc.status_code == 400 and str(exc.detail).startswith('Not an image file.')

Try / catch

try:
    await serve_image(name, filename)
except HTTPException as e:
    if e.status_code == 400 and str(e.detail).startswith('Not an image file.'):
        convert_to_png_and_retry(filename)
    raise

Prevention

When it happens

Trigger: Requesting an image-scoped route with e.g. 'clip.mp4', 'labels.json', 'cat.gif', 'img.tiff', or a file with no extension. Example: GET /training/diffusion/dataset/myset/image/notes.txt.

Common situations: Forgetting that videos/clips are managed by different endpoints than stills; trying to fetch a .tiff/.gif/.avif that the trainer does not accept; probing for sidecar files (.txt captions, metadata.jsonl) through the image route.

Related errors


AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15). Data as JSON: /api/errors/583ec255f58b0c80. Report an issue: GitHub.