Skip to content

fix(sync): auto-regenerate thumbnail sizes left stale after an image edit - #1314

Open
gabrielcld2 wants to merge 2 commits into
developfrom
fix/regenerate-outdated-thumbnail-sizes
Open

gabrielcld2 wants to merge 2 commits into
developfrom
fix/regenerate-outdated-thumbnail-sizes

Conversation

@gabrielcld2

@gabrielcld2 gabrielcld2 commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator

The issue

Editing an image via the WordPress image editor (crop/scale) can rename the main attached file (adding an -e{timestamp} suffix) without regenerating its registered thumbnail sizes.

The sizes metadata is left pointing at the pre-edit filenames, which no longer exist on disk and have no matching entry in wp_cloudinary_relationships.

Every render of that attachment then falls through to Delivery::find_attachment_size_urls(), which runs an uncached wp_postmeta query trying (and failing) to resolve the stale URL.

On high-traffic pages (e.g. a site icon used on every page) this becomes a query on every single request.

The fix

This adds a new sizes sync type, modeled directly on the existing edit sync type:

  • Media::has_outdated_sizes() (php/class-media.php) detects when the sizes metadata's filenames no longer match the current attached file's basename.
  • Upload_Sync::regenerate_sizes() (php/sync/class-upload-sync.php) calls wp_generate_attachment_metadata() to rebuild the sizes, then persists the new signature via set_signature_item() so it doesn't re-run on every subsequent render.

Because this rides the existing realtime sync pipeline, it self-heals both future edits and attachments that are already drifted today.

i.e. no new cron job, scheduled scan, or admin action required. No code changes needed outside these three files.

Limitations

  • already-drifted attachments that are never rendered anywhere won't be caught by this (nothing triggers the per-render check for them)
  • the image will appear fixed only from the second run. Given the current state this is a much better improvement that doesn't require us to maintain a complicated cron mechanism, so that seems an acceptable compromise.

QA notes

Requires an attachment already synced to Cloudinary (shows a Cloudinary status / has a Public ID in the Media Library) — this is what lets the drift-detection logic run on render.

Manual steps:

  1. In the Media Library, open that synced image → Edit Image → crop (or rotate) → Update. WordPress renames the file (adds -e{timestamp}) but, on develop, leaves the registered thumbnail sizes pointing at the old filename.
  2. Run the check script below → should print Outdated sizes: YES - bug present.
  3. Visit any front-end page where this image is rendered (or just open its attachment page).
  4. Run the check script again:
    • On develop: still YES - bug present — never fixes itself.
    • On this branch: now no - in sync, and the sizes list shows the new, edited filenames.
  5. (Optional) Visit the front-end page once more and run the script a third time — output should be unchanged, confirming it doesn't keep regenerating the thumbnails on every page load.

Script (qa-check-sizes.php) — read-only, auto-detects the most recently edited image attachment, no ID needed:

<?php
/**
 * Inspect the most recently edited image attachment (any attachment with
 * `_wp_attachment_backup_sizes`, which WordPress sets when you crop/rotate an
 * image) and report whether its registered thumbnail sizes still match the
 * current attached file.
 *
 * Run this right after editing an image in the Media Library, and again
 * after loading a front-end page that renders it, to see the effect of this
 * fix. No attachment ID needed — it finds the most recently edited one.
 *
 * Usage: wp eval-file qa-check-sizes.php
 */

$plugin = Cloudinary\get_plugin_instance();
$media  = $plugin->components['media'];

$attachments = get_posts(
	array(
		'post_type'      => 'attachment',
		'posts_per_page' => 1,
		'orderby'        => 'modified',
		'order'          => 'DESC',
		'meta_key'       => '_wp_attachment_backup_sizes',
	)
);

if ( empty( $attachments ) ) {
	WP_CLI::error( 'No edited image attachment found. Crop or rotate an image in the Media Library first.' );
}

$attachment_id = $attachments[0]->ID;
$metadata      = wp_get_attachment_metadata( $attachment_id );

WP_CLI::log( "Attachment: #$attachment_id (" . get_the_title( $attachment_id ) . ')' );
WP_CLI::log( 'Current attached file: ' . get_post_meta( $attachment_id, '_wp_attached_file', true ) );
WP_CLI::log( '' );
WP_CLI::log( 'Registered sizes:' );
foreach ( $metadata['sizes'] as $size => $data ) {
	WP_CLI::log( "  $size => {$data['file']}" );
}
WP_CLI::log( '' );
WP_CLI::log( 'Outdated sizes (stale filenames from before the edit): ' . ( $media->has_outdated_sizes( $attachment_id ) ? 'YES - bug present' : 'no - in sync' ) );

…edit

Editing an image via the WP image editor can rename the main file without
regenerating its registered thumbnail sizes, leaving `sizes` metadata
pointing at filenames that no longer exist. This causes
Delivery::find_attachment_size_urls() to run an uncached wp_postmeta
lookup on every render of that attachment, since nothing resolves the
stale URLs.

Adds a `sizes` sync type (mirroring the existing `edit` type) that
detects this drift on render via the existing realtime sync pipeline and
regenerates the attachment metadata, self-healing both new edits and
already-drifted attachments without any new cron job or bulk scan.
wp_get_attachment_metadata()'s PHPDoc already types 'sizes' as array,
making the is_array() check dead code; empty() already covers the
missing-key and empty-array cases.
@gabrielcld2
gabrielcld2 marked this pull request as ready for review October 12, 2026 12:38

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants