Skip to content

Upload Media: Endless sideload loop when the browser can't decode a JPEG that wasm-vips can #83512

Description

@adamsilverstein

I found this while recording demos for WordPress/wordpress-develop#12585, and it reproduces the same way in the block editor.

Claude traced the loop back to its source:

Description

Uploading a JPEG that wasm-vips can decode but the browser can't sends client-side media processing into an endless loop. The upload never finishes, and the same sub-sizes are sideloaded over and over (-103, -104, ... suffixes) until the tab is closed. In testing it restarted 33 times in 15 seconds, and the server got bogged down enough that new logins timed out.

The cause is in generateThumbnails. After the sub-size children are queued, the big-image threshold check calls createImageBitmap( thumbnailSource ) without a try/catch:

if ( bigImageSizeThreshold && attachment.id ) {
// Check if the image actually exceeds the threshold.
// Only create a scaled version for images larger than the threshold,
// matching WordPress core's wp_create_image_subsizes() behavior.
const bitmap = await createImageBitmap( thumbnailSource );
const needsScaling =
bitmap.width > bigImageSizeThreshold ||
bitmap.height > bigImageSizeThreshold;
bitmap.close();

wasm-vips decodes the damaged JPEG tolerantly, so the sub-sizes are generated fine, but createImageBitmap() rejects with "The source image could not be decoded." The thunk throws, finishOperation( id ) never runs, and the parent item stays on THUMBNAIL_GENERATION. Each child's upload completion then calls processItem( parentId ), which starts THUMBNAIL_GENERATION again and queues every sub-size again.

A likely fix is to guard the threshold check so a decode failure skips the -scaled version and still reaches finishOperation. The dimensions could also come from the attachment's reported width and height instead of decoding the file a second time.

Step-by-step reproduction instructions

  1. Make a partly damaged JPEG: take the first 6 KB of a real photo (a 2400x1600 one was used here) and append random bytes:
    head -c 6000 photo.jpg > damaged.jpg && head -c 20000 /dev/urandom >> damaged.jpg
  2. In Chrome 137+ with client-side media processing enabled, add an Image block and upload damaged.jpg.
  3. With SCRIPT_DEBUG on, the console shows Starting operation THUMBNAIL_GENERATION repeating, along with an uncaught "The source image could not be decoded." each time. The Network tab shows /wp/v2/media/<id>/sideload requests that never stop.

Expected: the upload finishes (with or without a -scaled size), or fails with an error.

Screenshots, screen recording, code snippet

Recorded in the Media Library grid using WordPress/wordpress-develop#12585, which runs the same bundled package. The overlay at the bottom right logs the upload requests:

Sideload loop

Environment info

AI Use

Investigated and drafted with 🤖 Claude Code.

Metadata

Metadata

Labels

[Status] In ProgressTracking issues with work in progress[Type] BugAn existing feature does not function as intended

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions