fix: probe images with a real decode before optimizing - #394
Merged
Conversation
The header-sniffing guard from #393 was not enough: the same garden failed again on a *truncated AVIF* renamed to .jpg. Its ftypavif header passes the magic-byte check, but sharp fails mid-decode ("bad seek"), and eleventy-img leaves internal per-format promise rejections permanently unhandled on decode failure — Eleventy's unhandledRejection handler then fails the build, regardless of the .catch we attach to the returned promise (verified empirically with the reporting garden's actual file). A valid header can never prove a decodable bitstream, so the picture transform now awaits isDecodableImage: header sniff as a cheap first filter, then an actual sharp decode (stats()), memoized per file mtime+size so each unique image is probed once per build. The probe resolves sharp through eleventy-img's own module resolution, so the answer always agrees with what the pipeline can do. Undecodable files keep their original <img> tag and log a warning naming the file. Fixture: a tiny AVIF truncated to 60%, which reproduces the exact "bad seek" failure in tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TFUucReuWzHi8VCKMovneB
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Same garden as #393, new failure: a truncated AVIF renamed to
.jpg(EyetotheEar.jpg, 34,033 bytes with the decoder seeking to 34,041). The #393 magic-byte guard passes it — theftypavifheader is perfectly valid — but sharp fails mid-decode, and the build dies again.Two findings from reproducing this locally with the actual file:
.catchnet cannot save the build. On decode failure, eleventy-img leaves internal per-format output promises permanently unhandled; Eleventy'sunhandledRejectionhandler goes fatal no matter what the caller catches. (rejectionHandledis only a warning — the fatal comes from the inner promises.)Fix
The picture transform (now async) awaits
isDecodableImage(path)before letting a file anywhere near eleventy-img:sharp(path).stats()— memoized per filemtime+size, so each unique image decodes once per build,<img>tag and log[image] <path> cannot be decoded and will not be optimized: <reason>.Testing
bad seekfailure; 265 total pass.Unhandled rejection in promise×2 and exit 1 despite the.catch; after — build succeeds, both bad files keep plain<img>tags, healthy images still get<picture>/optimized variants.No manifest change needed:
imageFormat.jsis already inplugin-info.jsonfrom #393; test + fixture files are intentionally not synced.🤖 Generated with Claude Code
https://claude.ai/code/session_01TFUucReuWzHi8VCKMovneB