{"record":{"id":"6c2fdd86e0819b7b","repo":"nexu-io/open-design","slug":"expected-png-or-webp-got-image-format","errorCode":null,"errorMessage":"expected PNG or WebP, got {image.format}","messagePattern":"expected PNG or WebP, got (.+?)","errorType":"exception","errorClass":"SystemExit","httpStatus":null,"severity":"error","filePath":"skills/hatch-pet/scripts/package_custom_pet.py","lineNumber":36,"sourceCode":"def default_codex_home() -> Path:\n    return Path(os.environ.get(\"CODEX_HOME\") or \"~/.codex\").expanduser().resolve()\n\n\ndef slugify(value: str) -> str:\n    value = value.strip().lower()\n    value = re.sub(r\"[^a-z0-9]+\", \"-\", value)\n    value = re.sub(r\"-{2,}\", \"-\", value)\n    return value.strip(\"-\")\n\n\ndef validate_spritesheet(path: Path) -> str:\n    with Image.open(path) as image:\n        if image.size != ATLAS_SIZE:\n            raise SystemExit(\n                f\"expected {ATLAS_SIZE[0]}x{ATLAS_SIZE[1]}, got {image.width}x{image.height}\"\n            )\n        if image.format not in {\"PNG\", \"WEBP\"}:\n            raise SystemExit(f\"expected PNG or WebP, got {image.format}\")\n        return str(image.format)\n\n\ndef write_webp_spritesheet(source: Path, target: Path, source_format: str) -> None:\n    if source_format == \"WEBP\":\n        shutil.copy2(source, target)\n        return\n    with Image.open(source) as image:\n        target.parent.mkdir(parents=True, exist_ok=True)\n        image.convert(\"RGBA\").save(\n            target,\n            format=\"WEBP\",\n            lossless=True,\n            quality=100,\n            method=6,\n        )\n\n","sourceCodeStart":18,"sourceCodeEnd":54,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/skills/hatch-pet/scripts/package_custom_pet.py#L18-L54","documentation":"Raised by validate_spritesheet when the spritesheet's detected format is neither PNG nor WEBP. The packager converts PNG to lossless WEBP, or copies WEBP verbatim; any other format is rejected because the conversion path does not trust it.","triggerScenarios":"Passing a JPEG, GIF, BMP, TIFF, or AVIF spritesheet. PIL reports image.format as e.g. \"JPEG\", which is not in {\"PNG\", \"WEBP\"}.","commonSituations":"Exported the sheet as JPEG (lossy, chroma artifacts); downloaded a GIF/WEBP variant that PIL identifies differently; saved from a tool that defaults to JPEG.","solutions":["Re-export/save the spritesheet as PNG (lossless) or WEBP.","Convert in place: python -c \"from PIL import Image; Image.open('sheet.jpg').save('sheet.png')\".","Re-run package_custom_pet.py --spritesheet sheet.png.","Confirm format: python -c \"from PIL import Image; print(Image.open('sheet.png').format)\" prints PNG or WEBP."],"exampleFix":"// before\npython package_custom_pet.py --spritesheet sheet.jpg ...\n# SystemExit: expected PNG or WebP, got JPEG\n\n// after\npython -c \"from PIL import Image; Image.open('sheet.jpg').save('sheet.png')\"\npython package_custom_pet.py --spritesheet sheet.png ...","handlingStrategy":"validation","validationCode":"from PIL import Image\n\ndef check_format(path):\n    with Image.open(path) as im:\n        if im.format not in {\"PNG\", \"WEBP\"}:\n            raise SystemExit(f\"expected PNG or WEBP, got {im.format}\")","typeGuard":"def is_png_or_webp(path) -> bool:\n    with Image.open(path) as im:\n        return im.format in {\"PNG\", \"WEBP\"}","tryCatchPattern":null,"preventionTips":["Export spritesheets as PNG (lossless) or WEBP; avoid JPEG/GIF for sprite atlases.","Verify format with Image.open(path).format before packaging.","Standardize the export preset in your art tool to PNG."],"tags":["spritesheet","image-format","validation","pillow"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}