{"record":{"id":"36f253720819e7d7","repo":"odysseus-dev/odysseus","slug":"invalid-input-image-e","errorCode":null,"errorMessage":"Invalid input image: {e}","messagePattern":"Invalid input image: (.+?)","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"scripts/mlx_image_server.py","lineNumber":183,"sourceCode":"    if snap.is_file():\n        return snap\n    candidates = sorted(snap.rglob(\"*.safetensors\"))\n    if not candidates:\n        raise HTTPException(500, f\"No safetensors weights found for {model} in {snap}\")\n    return candidates[0]\n\n\ndef _write_bridge_input_image(raw: bytes, out_path: Path) -> None:\n    try:\n        from PIL import Image\n        import io\n    except Exception as e:\n        raise HTTPException(503, \"Pillow is required for MLX image edit bridge inputs.\") from e\n    try:\n        img = Image.open(io.BytesIO(raw)).convert(\"RGBA\")\n        img.save(out_path, format=\"PNG\")\n    except Exception as e:\n        raise HTTPException(400, f\"Invalid input image: {e}\") from e\n\n\ndef _write_bridge_mask(raw: bytes, out_path: Path) -> None:\n    try:\n        from PIL import Image\n        import io\n    except Exception as e:\n        raise HTTPException(503, \"Pillow is required for MLX image edit bridge masks.\") from e\n    try:\n        img = Image.open(io.BytesIO(raw))\n        if img.mode == \"RGBA\":\n            # OpenAI edits mask convention: transparent = regenerate.\n            alpha = img.getchannel(\"A\")\n            mask = alpha.point(lambda p: 255 if p < 128 else 0)\n        else:\n            mask = img.convert(\"L\")\n        mask.save(out_path, format=\"PNG\")\n    except Exception as e:","sourceCodeStart":165,"sourceCodeEnd":201,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/scripts/mlx_image_server.py#L165-L201","documentation":"Raised by _write_bridge_input_image in scripts/mlx_image_server.py when PIL fails to open, convert to RGBA, or PNG-save the uploaded edit image (any exception inside the try block is chained into HTTP 400). It signals malformed or unsupported image bytes rather than a server fault.","triggerScenarios":"Uploading a corrupted file, a non-image file (e.g. PDF/text renamed .png), a truncated upload, or an image format PIL cannot decode; occasionally an unwritable temp path makes img.save raise the same way.","commonSituations":"Client sends base64-decoded garbage or empty bytes; multipart field order sends the wrong file into 'image'; progressive/interrupted upload; image saved with a .png extension but actually WebP/HEIC and PIL lacks the decoder plugin.","solutions":["Validate the file locally before upload: open it with PIL and re-save as PNG","Confirm the multipart field named 'image' actually contains the image and is fully transmitted","If the file is HEIC/AVIF/etc., convert it to PNG or JPEG client-side first","Check server temp directory is writable if uploads are known-good"],"exampleFix":"# before\nfiles = {'image': open('possibly-broken.png','rb')}\n# after\nfrom PIL import Image\nimg = Image.open('possibly-broken.png'); img.verify()\nfiles = {'image': open('known-good.png','rb')}","handlingStrategy":"validation","validationCode":"from PIL import Image\nimport io\nimg = Image.open(io.BytesIO(raw_bytes)); img.verify()  # raises before upload if broken\nimg = Image.open(io.BytesIO(raw_bytes)).convert('RGB')\nimg.save('/tmp/out.png', format='PNG')  # exercise save too","typeGuard":null,"tryCatchPattern":"try:\n    r = requests.post(f'{base}/v1/images/edits', files=files)\n    r.raise_for_status()\nexcept requests.HTTPError as e:\n    if e.response.status_code == 400:\n        fix_and_recompress_image(); retry_once()","preventionTips":["Always verify()+re-save images as PNG client-side","Never trust file extensions; validate magic bytes","Stream multipart from a real file object, not a text buffer"],"tags":["pillow","validation","http-400","image-upload"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}