invoke-ai/InvokeAI · warning · HTTPException

Failed to read image

Error message

Failed to read image

What it means

HTTP 415 raised when PIL (Pillow) fails to open the uploaded bytes as an image. The endpoint logs the full traceback server-side and returns 'Failed to read image', so the file was claimed to be an image but its content is undecodable.

Source

Thrown at invokeai/app/api/routers/workflows.py:295

    try:
        existing = await asyncio.to_thread(ApiDependencies.invoker.services.workflow_records.get, workflow_id)
    except WorkflowNotFoundError:
        raise HTTPException(status_code=404, detail="Workflow not found")

    config = ApiDependencies.invoker.services.configuration
    if config.multiuser and not current_user.is_admin and existing.user_id != current_user.user_id:
        raise HTTPException(status_code=403, detail="Not authorized to update this workflow")

    if not image.content_type or not image.content_type.startswith("image"):
        raise HTTPException(status_code=415, detail="Not an image")

    contents = await image.read()
    try:
        pil_image = await asyncio.to_thread(Image.open, io.BytesIO(contents))

    except Exception:
        ApiDependencies.invoker.services.logger.error(traceback.format_exc())
        raise HTTPException(status_code=415, detail="Failed to read image")

    try:
        await asyncio.to_thread(ApiDependencies.invoker.services.workflow_thumbnails.save, workflow_id, pil_image)
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))


@workflows_router.delete(
    "/i/{workflow_id}/thumbnail",
    operation_id="delete_workflow_thumbnail",
    responses={
        200: {"model": WorkflowRecordDTO},
    },
)
def delete_workflow_thumbnail(
    current_user: CurrentUserOrDefault,
    workflow_id: str = Path(description="The workflow to update"),
):

View on GitHub (pinned to 0b6a024f2f)

Solutions

  1. Verify the file opens with any image viewer / PIL locally before uploading
  2. Convert to PNG or JPEG client-side and re-upload
  3. Ensure the upload reads raw binary (await file.arrayBuffer()/blob) without text encoding
  4. Check server logs for the PIL traceback to identify the exact decode failure

Example fix

// before
await fetch(url, {method:'POST', body: JSON.stringify({data: base64})}); // server gets garbage
// after
const blob = await file.arrayBuffer();
await fetch(url, {method:'POST', body: new Blob([blob], {type: file.type})});
Defensive patterns

Strategy: try-catch

Validate before calling

// decode client-side first to guarantee Pillow can read it
const img = await createImageBitmap(file); // throws if not a decodable image

Type guard

function isDecodableImage(f) { try { return !!f.size && f.type.startsWith('image/'); } catch { return false; } }

Try / catch

try { await uploadThumbnail(id, file); } catch (e) { if (e?.status === 415) notify('Image could not be decoded; re-export as PNG'); else throw e; }

Prevention

When it happens

Trigger: Uploading corrupted/truncated image data, an empty body, or a file with image/* Content-Type but non-image content (e.g. renamed JSON/HTML/SVG).

Common situations: Zero-byte files from aborted client reads; SVGs or WebP variants Pillow's build can't decode; upload middleware mangling binary data (UTF-8 re-encoding); truncated downloads before upload.

Related errors


AI-assisted analysis of invoke-ai/InvokeAI@0b6a024f2f (2026-08-29). Data as JSON: /api/errors/600b688698393315. Report an issue: GitHub.