Skip to content

[6.x] Drop a listed asset when an image read finds the file gone - #15850

Open
aerni wants to merge 12 commits into
statamic:6.xfrom
aerni:fix/missing-asset-on-read
Open

aerni wants to merge 12 commits into
statamic:6.xfrom
aerni:fix/missing-asset-on-read

Conversation

@aerni

@aerni aerni commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

The diff includes #15833 because this has to target 6.x. That branch only exists on the fork, so it can't be the base here. The new change is f22809b.

Issue

#15833 drops a listed asset when a metadata read finds the file gone. That happens in generateMeta(). With a warm meta cache the size and mime type are already stored, so opening the asset never calls generateMeta() and the path stays in the cached directory listing.

The Control Panel builds the folder from that listing. The thumbnail is a later request. It opens the file, Glide fails, and the response is a 404. The row is still there on the next load.

Solution

This builds on #15833. When image generation reports the source missing, confirm the file is gone with disk()->exists(), then call forgetMissingFile() from that PR. The path leaves the listing and, unless assets use the eloquent driver, the Stache index. Stored asset data stays put: the .meta file and an eloquent meta row are left alone.

The folder response already sent still includes the row. The next load omits it. A cached manipulation never opens the source, so the path remains until that cache misses and a generation confirms the file is missing. An existence check that throws, such as a timeout or a 403, propagates and leaves the listing unchanged.

aerni added 12 commits November 10, 2026 12:51
A cached container listing can still name a file that is no longer on disk. Reading that asset asked Flysystem for its size and threw, and the path was still handed back as the current asset.
reconcile() drops a listed path whose file is gone. exists() stays a listing lookup, so callers can tell those two steps apart.
The file metadata is only merged after those reads succeed, so a throw leaves the original data array for the existing return.
A missing file is dropped during meta generation. writeMeta() now checks the listing itself, so no caller can save a meta file for that path.
The repository delete updates the listing and the asset index without reading metadata, so it never calls forgetMissingFile() again.
A Stache asset is not missing just because the container listing does not name it yet. Only a listed path whose file is gone should be reconciled and dropped.
The asset repository's eloquent delete removes the meta row. Forget the cached path directly so a missing file no longer takes authored data with it.
A timeout or a 403 throws the same metadata exceptions as a missing file. Only drop the path when the disk reports it missing, and leave a failed read uncached so the next request can try again.
A missing file is dropped when its metadata is generated. Lookups and queries only need the listing, so the public reconcile method can go.
Drop assertions that only described behavior removed before the pull request, and name the remaining tests like the rest of the file.
A warm meta cache never reaches generateMeta(), so a deleted file stayed in the listing after the thumbnail 404'd.
@aerni
aerni marked this pull request as ready for review November 11, 2026 11:15

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.

1 participant