{"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":"error","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/199723261c6ffa897fec8ccdaea6359e39c37cc3/machine-learning/immich_ml/main.py#L157-L193","documentation":"Raised as HTTPException(400) by the /predict endpoint when the uploaded image decodes successfully (PIL) but has width==0 or height==0. The decode succeeds but the resulting image is degenerate and cannot be used for inference.","triggerScenarios":"POST /predict with an image file that PIL can decode but which has zero dimension(s) — a corrupt/truncated image header reporting 0x0, or an intentionally crafted edge-case image.","commonSituations":"Corrupt upload from a flaky mobile sync; partially-written file on disk before the ML request; re-encoded by a faulty transcode/thumbnail pipeline; a 0-byte or header-only file that some decoder tolerates.","solutions":["Re-upload or re-encode the source image so it has valid non-zero dimensions.","On the server side, validate the asset file integrity (e.g. re-run metadata extraction) before queuing ML jobs.","If only a few assets are affected, exclude/regenerate them via the CLIP/duplicate jobs after fixing the file."],"exampleFix":"# before\nimg = PILImage.open(path)\nimg.load()  # may yield 0x0\n\n# after\nimg = PILImage.open(path)\nif not img.width or not img.height:\n    raise ValueError(f'bad dimensions {img.size} for {path}')","handlingStrategy":"validation","validationCode":"from PIL import Image\ndef valid_image(b: bytes) -> bool:\n    try:\n        im = Image.open(io.BytesIO(b)); im.load()\n        return im.width > 0 and im.height > 0\n    except Exception:\n        return False","typeGuard":"def has_valid_dimensions(im) -> bool:\n    return getattr(im, 'width', 0) > 0 and getattr(im, 'height', 0) > 0","tryCatchPattern":"try:\n    decoded = await run(lambda: decode_pil(image))\nexcept Exception:\n    raise HTTPException(400, 'Image could not be decoded')","preventionTips":["Validate uploaded image dimensions before queuing ML jobs.","Re-run metadata extraction for assets that fail."],"tags":["machine-learning","image","validation","predict","corrupt-file"],"backgroundTag":null,"analyzedSha":"199723261c6ffa897fec8ccdaea6359e39c37cc3","analyzedAt":"2026-08-12T04:54:27.085Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}