Skip to content

Hybrid Glide: asset reuploads leave broken image URLs in full static cache, even with rules=all #15512

Description

@florianbouvot

Bug description

With Statamic 6.34.0, hybrid Glide caching and full static caching, reuploading an existing asset deletes the generated image and its hybrid source mapping but leaves the cached HTML in place. The image URL in that HTML subsequently returns HTTP 404 until the page is rendered again.

This reproduces with invalidation.rules = 'all', QUEUE_CONNECTION=sync, and background recaching disabled. No custom invalidator, addon, CDN, or asynchronous worker is needed.

Related to #15432, but this is a different trigger: that report concerns focal-point saves with missing asset invalidation rules. Here even rules = 'all' does not help, because a reupload emits AssetReuploaded, not AssetSaved, and the static-cache subscriber does not listen for it.

How to reproduce

  1. Use Statamic 6.34.0 with:
    // config/statamic/assets.php
    'image_manipulation' => [
        'route' => 'img',
        'cache' => 'hybrid',
        'cache_path' => public_path('img'),
    ],
    
    // config/statamic/static_caching.php
    'strategy' => 'full',
    'invalidation' => ['class' => null, 'rules' => 'all'],
    'background_recache' => false,
    Use the synchronous queue.
  2. Render a page containing <img src="{{ glide:photo width='100' }}"> and let both the HTML and image be cached.
  3. In the CP, reupload the asset under the same path, or upload using Overwrite Existing File. The crop editor's replace-original path also calls Asset::reupload().
  4. Request the page and its image again, bypassing browser caching.

Expected: the affected cached HTML is invalidated so it can rebuild valid hybrid image mappings.

Actual: the generated image and hybrid::<path> mapping are removed, but the HTML file remains. The unchanged cached HTML continues pointing at a URL returning 404.

Automated reproduction in the CMS repository

I reproduced this in an otherwise unmodified checkout of 6.x at a1da79e8e84c371f0d5a35a92a1561b8d910c12b (6.34.0), using the existing Tests\Imaging\GlideTest helpers. Add these methods to that test class:

#[Test]
#[DefineEnvironment('hybridAndFullCaching')]
public function reuploading_an_asset_invalidates_full_cached_html_with_hybrid_urls()
{
    $asset = $this->createAsset();
    $url = $this->app->make(UrlBuilder::class)->build($asset, ['w' => 100]);
    $request = \Illuminate\Http\Request::create('http://localhost/hybrid-reupload');
    $cacher = app(\Statamic\StaticCaching\Cacher::class);

    try {
        $cacher->cachePage($request, '<img src="'.$url.'">');
        $this->assertTrue($cacher->hasCachedPage($request));
        $this->get($url)->assertOk()->streamedContent();

        $replacement = UploadedFile::fake()->image('replacement.jpg', 60, 30);
        Storage::fake('local')->put('replacement.jpg', file_get_contents($replacement->getPathname()));
        $asset->reupload(new \Statamic\Assets\ReplacementFile('replacement.jpg'));

        $this->assertFileDoesNotExist(public_path(rawurldecode($url)));
        $this->get($url)->assertNotFound();
        $this->assertFalse($cacher->hasCachedPage($request),
            'Full HTML still references the deleted hybrid image mapping.');
    } finally {
        $cacher->flush();
    }
}

protected function hybridAndFullCaching($app)
{
    $this->hybridCaching($app);
    $app['config']->set('statamic.static_caching.strategy', 'full');
    $app['config']->set('statamic.static_caching.strategies.full.path', public_path('static'));
    $app['config']->set('statamic.static_caching.invalidation.rules', 'all');
    $app['config']->set('statamic.static_caching.background_recache', false);
    $app['config']->set('queue.default', 'sync');
}

Run:

vendor/bin/phpunit tests/Imaging/GlideTest.php --filter=reuploading_an_asset_invalidates_full_cached_html_with_hybrid_urls

Logs

The test reaches the final assertion and fails:

Full HTML still references the deleted hybrid image mapping.
Failed asserting that true is false.

The preceding assertions confirm successful initial generation, removal of the generated file, and HTTP 404 after reupload.

Environment

This reproduction runs directly in the CMS repository through Testbench, rather than a standalone Statamic application, so there is no production application's support report.

CMS checkout: 6.x at a1da79e8e84c371f0d5a35a92a1561b8d910c12b (6.34.0)
PHP: 8.5.8
Laravel: 13.33.0
Orchestra Testbench: 11.2.0
PHPUnit: 12.5.33
league/glide: 4.1.0
intervention/image: 4.3.2
OS: macOS
Static caching: full
Glide caching: hybrid
Invalidation rules: all
Queue: sync
Background recache: false
Addons: none in the reproduction

Installation

Other (please explain)

Additional details

Installation: fresh clone of the CMS repository with Composer development dependencies and the regression test above.

Asset::reupload() dispatches AssetReuploaded. ClearAssetGlideCache handles that event and removes the image files and hybrid mappings. StaticCaching\Invalidate subscribes to AssetSaved and AssetDeleted, but not AssetReuploaded.

A native fix could invalidate the affected static pages on asset reupload, with ordering that ensures invalidation happens after Glide files/mappings are cleared. Both rules = 'all' and container-specific invalidation rules should be covered. This report is about missing server-side HTML invalidation, independently of browser cache headers.

The manual glide:clear / HTML-rewarm requirement mentioned in #14570 is understood; the problematic trigger here is an ordinary editorial operation in the CP.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions