{"record":{"id":"1dae671d455d89f8","repo":"unslothai/unsloth","slug":"image-original-name-is-too-large-maximum-is","errorCode":null,"errorMessage":"Image '{original_name}' is too large; maximum is {_MAX_TRAINING_IMAGE_SIDE}px per side.","messagePattern":"Image '(.+?)' is too large; maximum is (.+?)px per side\\.","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"studio/backend/routes/training.py","lineNumber":3649,"sourceCode":"\n# Match diffusion's 4096px decoded-image limit.\n_MAX_TRAINING_IMAGE_SIDE = 4096\n\n\ndef _validate_uploaded_training_image(path: Path, original_name: str) -> None:\n    \"\"\"Reject an uploaded training image whose decoded dimensions exceed the per-side limit.\n\n    Reads only the header (never img.load()), so a small-payload / huge-dimension file is caught\n    before it spikes memory. Bytes PIL cannot identify are left as-is (the upload contract accepts\n    arbitrary bytes under an image extension), so only oversized real images change behaviour.\"\"\"\n    from PIL import Image, UnidentifiedImageError\n\n    try:\n        with Image.open(path) as image:\n            width, height = image.size\n    except Image.DecompressionBombError:\n        # Past Pillow's ~179 MP limit Image.open() raises before .size can be read, with an error deriving straight from Exception, so letting it escape would 500 the upload.\n        raise HTTPException(\n            status_code = 400,\n            detail = (\n                f\"Image '{original_name}' is too large; maximum is \"\n                f\"{_MAX_TRAINING_IMAGE_SIDE}px per side.\"\n            ),\n        )\n    except (OSError, UnidentifiedImageError, ValueError):\n        return  # not a decodable image -> not a bomb; leave the existing contract\n    if width > _MAX_TRAINING_IMAGE_SIDE or height > _MAX_TRAINING_IMAGE_SIDE:\n        raise HTTPException(\n            status_code = 400,\n            detail = (\n                f\"Image '{original_name}' is too large ({width}x{height}); maximum is \"\n                f\"{_MAX_TRAINING_IMAGE_SIDE}px per side.\"\n            ),\n        )\n\n","sourceCodeStart":3631,"sourceCodeEnd":3667,"githubUrl":"https://github.com/unslothai/unsloth/blob/203007d19051dcd2ae33876786d117c99f6b0368/studio/backend/routes/training.py#L3631-L3667","documentation":"HTTP 400 from _validate_uploaded_training_image: Pillow raised Image.DecompressionBombError while opening the uploaded file, meaning the image header declares dimensions past Pillow's ~179-megapixel bomb limit, so the check could not even read .size. The route converts this into a clean 400 (max 4096px per side) instead of an unhandled 500, since DecompressionBombError derives straight from Exception. Only the header is read — no pixels are decoded.","triggerScenarios":"Uploading (in a diffusion dataset batch) an image file under an image extension (.png/.jpg/.jpeg/.webp/.bmp) whose header claims width*height > ~179 MP. A tiny 50 KB PNG declaring 20000x20000 pixels triggers it during the upload's per-image validation pass.","commonSituations":"Malicious or accidental decompression bombs in scraped datasets; AI-upscaler outputs with huge dimension metadata; corrupt files with bogus headers; testing tools that push extreme dimensions.","solutions":["Reject/replace the offending image (the message names the file).","Re-encode the image at sane dimensions (<=4096px per side): pngcrush/magick convert with resize, or strip bogus metadata.","If a whole scraped dataset contains many bombs, batch-normalize it before upload: magick mogrify -resize '4096x4096>' *.png."],"exampleFix":"# re-encode oversized images before upload\nmagick identify -format '%f %wx%h\\n' *.png | awk '$2+0>4096 || $3+0>4096'  # find offenders\nmagick big.png -resize '4096x4096>' big.png","handlingStrategy":"validation","validationCode":"from PIL import Image\n\ndef safe_to_upload(path: str) -> bool:\n    try:\n        with Image.open(path) as im:\n            w, h = im.size\n        return w <= 4096 and h <= 4096\n    except Exception:\n        return False  # let the server's non-decodable contract decide","typeGuard":"def is_image_dimension_error(exc: HTTPException) -> bool:\n    return exc.status_code == 400 and 'too large' in exc.detail","tryCatchPattern":"try:\n    resp = await client.post(upload_url, files=batch)\nexcept httpx.HTTPStatusError as e:\n    if e.response.status_code == 400 and 'too large' in e.response.json()['detail']:\n        skip_and_log_offender(e.response.json()['detail'])\n        continue\n    raise","preventionTips":["Pre-scan dataset images' headers with PIL before uploading; skip anything over 4096px per side.","Keep Pillow's MAX_IMAGE_PIXELS at its default; do not disable the bomb check.","Validate third-party/scraped datasets before they reach the trainer."],"tags":["security","decompression-bomb","pillow","image","http-400"],"backgroundTag":null,"analyzedSha":"203007d19051dcd2ae33876786d117c99f6b0368","analyzedAt":"2026-08-15T02:48:39.846Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}