{"record":{"id":"7ba158ca3dfc78fa","repo":"immich-app/immich","slug":"image-has-zero-width-or-height","errorCode":null,"errorMessage":"Image has zero width or height","messagePattern":"Image has zero width or height","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"machine-learning/immich_ml/main.py","lineNumber":175,"sourceCode":"async def root() -> ORJSONResponse:\n    return ORJSONResponse({\"message\": \"Immich ML\"})\n\n\n@app.get(\"/ping\")\ndef ping() -> PlainTextResponse:\n    return PlainTextResponse(\"pong\")\n\n\n@app.post(\"/predict\", dependencies=[Depends(update_state)])\nasync def predict(\n    entries: InferenceEntries = Depends(get_entries),\n    image: bytes | None = File(default=None),\n    text: str | None = Form(default=None),\n) -> Any:\n    if image is not None:\n        decoded = await run(lambda: decode_pil(image))\n        if decoded.width == 0 or decoded.height == 0:\n            raise HTTPException(400, \"Image has zero width or height\")\n        inputs: Image | str = decoded\n    elif text is not None:\n        inputs = text\n    else:\n        raise HTTPException(400, \"Either image or text must be provided\")\n    response = await run_inference(inputs, entries)\n    return ORJSONResponse(response)\n\n\nasync def run_inference(payload: Image | str, entries: InferenceEntries) -> InferenceResponse:\n    outputs: dict[ModelIdentity, Any] = {}\n    response: InferenceResponse = {}\n\n    async def _run_inference(entry: InferenceEntry) -> None:\n        model = await model_cache.get(\n            entry[\"name\"], entry[\"type\"], entry[\"task\"], ttl=settings.model_ttl, **entry[\"options\"]\n        )\n        inputs = [payload]","sourceCodeStart":157,"sourceCodeEnd":193,"githubUrl":"https://github.com/immich-app/immich/blob/e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c/machine-learning/immich_ml/main.py#L157-L193","documentation":"predict validates the uploaded image before inference. If decode_pil produces an image whose width or height is 0, the endpoint raises HTTP 400 'Image has zero width or height' because downstream models cannot process a degenerate image.","triggerScenarios":"Uploading a corrupted/truncated image, an image whose header declares zero dimensions, or an empty/partial byte stream that PIL decodes into a zero-sized image.","commonSituations":"Thumbnailing a file that was still being written; interrupted uploads; exotic formats PIL partially decodes; test fixtures with empty buffers.","solutions":["Check the source file is a fully written, non-empty image before sending it to the ML endpoint.","Re-encode or re-fetch the asset; the original data is corrupt.","On the client, pre-validate image dimensions before calling predict."],"exampleFix":"# before\nrequests.post(f\"{ml}/predict\", files={\"image\": open(p, 'rb')})\n# after\nfrom PIL import Image\nimg = Image.open(p); img.verify()\nwith Image.open(p) as im:\n    if im.width == 0 or im.height == 0:\n        skip(p)\n    else:\n        requests.post(f\"{ml}/predict\", files={\"image\": open(p, 'rb')})","handlingStrategy":"validation","validationCode":"from PIL import Image\nwith Image.open(path) as im:\n    im.verify()\nwith Image.open(path) as im:\n    assert im.width > 0 and im.height > 0","typeGuard":null,"tryCatchPattern":"try:\n    r = requests.post(f\"{ml}/predict\", files={\"image\": f})\n    r.raise_for_status()\nexcept requests.HTTPError as e:\n    if e.response.status_code == 400: skip_asset(asset_id)","preventionTips":["Verify image integrity client-side before upload.","Skip zero-byte or still-being-written files.","Re-fetch/re-encode assets that fail repeatedly."],"tags":["image","validation","fastapi","pillow"],"backgroundTag":"invalid-argument-value","analyzedSha":"e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c","analyzedAt":"2026-09-15T07:20:19.675Z","contentChangedAt":"2026-09-15T07:20:19.675Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}