You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Implements the PR check requested in #15957 ("can we do a PR check to validate that the extension matches the actual file content?"). #15957 covers cleaning up the images that are already wrong; this one is about stopping new ones getting in, and can land independently of that cleanup.
The problem
An image whose contents do not match its extension (a WebP saved as .png is the common one) builds fine on our pinned Hugo 0.152.2, because Hugo sniffs the content rather than trusting the name. Newer Hugo does not, and fails the build. So these land in the repo invisibly and only surface later, at the worst possible moment — during a version bump, on someone else's PR.
It is nobody's fault: saving an image from a browser frequently produces a WebP, and utilities/add_sponsors.sh hardcodes a .png destination regardless of what you feed it (#16150).
The constraint that shapes the design
A check over the whole repo is not viable. Counting current tracked image files under assets/ and static/:
total tracked image files: 17543
violations: 857
Broken down by what they actually are:
Real format
Count
JPEG
623
PNG
172
WebP
57
GIF
5
(Note it goes both ways — 172 are real PNGs carrying a .jpg name.)
A repo-wide check would therefore fail every pull request from the day it merged, until all 857 were fixed. That would be miserable for organizers who have nothing to do with it.
So the check must only look at files changed in the pull request. New mismatches are blocked immediately; the backlog gets cleaned up separately under #15957 at whatever pace suits. Most of the 857 are harmless today (a JPEG named .png works on both old and new Hugo); only the 57 WebP and 5 GIF actually break newer Hugo.
Proposed implementation
A step in the existing lint job in .github/workflows/hugo.yml. No new action or dependency — file is already on ubuntu-latest.
- name: Lint image formatsenv:
PR_NUMBER: ${{ github.event.pull_request.number }}REPO: ${{ github.repository }}GH_TOKEN: ${{ github.token }}run: | # Image files whose contents do not match their extension build fine on # the pinned Hugo but break on newer versions. Only files changed in this # PR are checked - the repo has a backlog of pre-existing mismatches # (see #15957). CHANGED=$(gh pr diff "$PR_NUMBER" --repo "$REPO" --name-only \ | grep -iE '\.(png|jpg|jpeg|gif|webp)$' || true) [ -z "$CHANGED" ] && { echo "No image files changed."; exit 0; } fail=0 while IFS= read -r f; do [ -f "$f" ] || continue # deleted in this PR ext=$(printf '%s' "${f##*.}" | tr '[:upper:]' '[:lower:]') mime=$(file --mime-type -b "$f") case "$ext:$mime" in png:image/png|jpg:image/jpeg|jpeg:image/jpeg|gif:image/gif|webp:image/webp) ;; *) case "$mime" in image/png) want=png ;; image/jpeg) want=jpg ;; image/gif) want=gif ;; image/webp) want=webp ;; *) want="" ;; esac if [ -n "$want" ]; then echo "::error file=$f::$f is actually $mime. Convert it to a real .$ext, or rename it to .$want and update the reference." else echo "::error file=$f::$f is not a recognised image ($mime)." fi fail=1 ;; esac done <<< "$CHANGED" if [ "$fail" -eq 1 ]; then echo "" echo "Saving an image from a browser often produces a WebP with a .png name." echo "Convert with: magick broken.png png:fixed.png" exit 1 fi echo "All changed image files match their extensions."
Using ::error file=...:: means GitHub annotates the offending file directly in the PR's Files Changed tab, which is the bit that actually helps a non-technical organizer.
I tested the detection logic locally against real files in this repo: it correctly flags assets/sponsors/c/clio.png (WebP) and assets/sponsors/o/oracle-before-20251111.png (GIF), and correctly passes assets/sponsors/d/datadog.png (a real PNG) and assets/events/2026-boston/speakers/dan-morgan.webp (a genuine .webp).
Things worth deciding before implementing
Hard fail or warning? I have written it as a hard fail, matching the filename lint. The counter-argument is that it blocks a sponsor addition over something Hugo currently tolerates. A warning would not stop the bleeding though, and this is trivially fixable once you know.
Renaming vs converting. The error offers both. Renaming is wrong for sponsor logos, because the filename must keep matching data/sponsors/<id>.yml — converting is nearly always the right fix there. The message could be smarter about that for paths under assets/sponsors/.
SVG is deliberately excluded. file reports SVGs inconsistently (text/xml, text/plain, image/svg+xml) and would produce false positives.
Once the cleanup in Many images lie about their formats. #15957 is done, this could be widened from "changed files" to the whole repo, which would also catch anything that slipped in before the check existed.
Implements the PR check requested in #15957 ("can we do a PR check to validate that the extension matches the actual file content?"). #15957 covers cleaning up the images that are already wrong; this one is about stopping new ones getting in, and can land independently of that cleanup.
The problem
An image whose contents do not match its extension (a WebP saved as
.pngis the common one) builds fine on our pinned Hugo 0.152.2, because Hugo sniffs the content rather than trusting the name. Newer Hugo does not, and fails the build. So these land in the repo invisibly and only surface later, at the worst possible moment — during a version bump, on someone else's PR.It is nobody's fault: saving an image from a browser frequently produces a WebP, and
utilities/add_sponsors.shhardcodes a.pngdestination regardless of what you feed it (#16150).The constraint that shapes the design
A check over the whole repo is not viable. Counting current tracked image files under
assets/andstatic/:Broken down by what they actually are:
(Note it goes both ways — 172 are real PNGs carrying a
.jpgname.)A repo-wide check would therefore fail every pull request from the day it merged, until all 857 were fixed. That would be miserable for organizers who have nothing to do with it.
So the check must only look at files changed in the pull request. New mismatches are blocked immediately; the backlog gets cleaned up separately under #15957 at whatever pace suits. Most of the 857 are harmless today (a JPEG named
.pngworks on both old and new Hugo); only the 57 WebP and 5 GIF actually break newer Hugo.Proposed implementation
A step in the existing
lintjob in.github/workflows/hugo.yml. No new action or dependency —fileis already onubuntu-latest.Using
::error file=...::means GitHub annotates the offending file directly in the PR's Files Changed tab, which is the bit that actually helps a non-technical organizer.I tested the detection logic locally against real files in this repo: it correctly flags
assets/sponsors/c/clio.png(WebP) andassets/sponsors/o/oracle-before-20251111.png(GIF), and correctly passesassets/sponsors/d/datadog.png(a real PNG) andassets/events/2026-boston/speakers/dan-morgan.webp(a genuine.webp).Things worth deciding before implementing
data/sponsors/<id>.yml— converting is nearly always the right fix there. The message could be smarter about that for paths underassets/sponsors/.filereports SVGs inconsistently (text/xml,text/plain,image/svg+xml) and would produce false positives.Related
add_sponsors.shhardcoding.pngfor the destination, which is one of the ways these get created