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
- Use an allowed extension: convert the file (e.g. magick in.gif out.png) before requesting/uploading it.
- Fetch captions via the dataset listing endpoints, not the image route; clips go through the clip endpoints.
- 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
- Filter client-side file pickers to the five allowed extensions.
- Convert .gif/.tiff/.avif assets during dataset preparation.
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
- Invalid image filename.
- Caption too long (max {_MAX_CAPTION_CHARS} characters).
- Invalid repo_id: {repo_id!r}
- Unsupported local dataset directory (expected parquet/json/j
- Unsupported file format: {dataset_path.suffix}
AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15).
Data as JSON: /api/errors/583ec255f58b0c80.
Report an issue: GitHub.