fix(docker): office image no longer gets :latest on a release — latest=false - #184
Open
MrChengLen wants to merge 1 commit into
Open
MrChengLen wants to merge 1 commit into
MrChengLen wants to merge 1 commit into
Conversation
…t=false
docker/metadata-action's default flavor, latest=auto, adds a `latest` tag
of its own whenever a type=semver entry matches a non-prerelease tag, and
a tag entry's suffix= does not reach that tag. On every v* release both
matrix legs therefore pushed :latest, and whichever finished last kept
it — usually the office image, the slower build. Anyone pulling :latest
then got LibreOffice without choosing the office image, and with the
default FILEMORPH_OFFICE_ENGINE=auto complex DOCX files went through it.
The v1.1.0 run's logs have expired; its timestamps show the office
build-and-push step ending 12 s after the slim one's, so on 2026-06-01
:latest was most likely the office image for about an hour, until the
next main build. Main builds were never affected: the action adds
`latest` only for tag refs.
flavor: latest=false leaves the type=raw entry as the only source of
:latest, set for the base leg; the office image keeps :office, and every
other tag is unchanged. Rejected: keeping latest=auto for the base leg
only (the raw entry already tags it on every build) and a per-leg
`suffix=-office,onlatest=true` flavor (it would add a new
:latest-office tag).
Confirmed from the action's source at the pinned SHA (src/flavor.ts;
src/meta.ts procSemver, setVersion, generateTags; identical in v6.1.0,
which built v1.1.0) and by running its dist/index.cjs (blob 9edb5c8,
hash-checked) locally on the workflow's inputs, against a localhost API
stub and without credentials: before, tag v1.2.0 gave the office leg
`latest`; after, only the slim leg gets it, on a release tag, a
pre-release tag and main.
Guard: test_only_the_slim_image_is_tagged_latest pins the flavor whole
(the action skips only lines starting with `#` and unquotes CSV fields,
so a looser match passed flavors it reads as latest=auto or
latest=true), fails if another tag entry of the step names `latest`
(an indented `# ...` line is an entry to the action, not a comment),
and fails if the matrix loses the unsuffixed base leg the raw entry is
meant for. Of 26 mutations run through both the guard and the real
action, the guard fails all 15 that tag the wrong image, plus 3 more
(an entry the action rejects, a `${{ vars.X }}` line, a swapped-out
action); 4 controls pass; 4 equivalent spellings fail on purpose. Both
reviews' suggestions are folded in.
docker.yml pushes images, so it was not dispatched on this branch; the
first main run shows the new flavor in its log, and the first real check
of the release tags is the next release (main runs create no semver
tags). Found by the security review of PR #180.
Full suite 1489 green (72 skipped on Windows); ruff clean; gitleaks and
the scope guard clean. No dependency, template or i18n change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MrChengLen
enabled auto-merge
September 30, 2026 13:31
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.
What
docker.yml's "Extract metadata" step (docker/metadata-action v6.2.0, pinned) getsflavor: latest=false.:latestnow comes only from the existingtype=rawentry, which names it for the slim (base) leg; the office image keeps:office. Every other tag is unchanged.Why
metadata-action's default flavor,
latest=auto, adds alatesttag of its own whenever atype=semverentry matches a non-prerelease tag. A tag entry'ssuffix=-officedoes not apply to it:src/meta.tsrunsprocSemver→setVersion(latest=true), andgenerateTagsbuildslatestfrom the global flavor prefix/suffix only. So on everyv*release both matrix legs pushed:latest, and whichever finished last kept it, usually the office image (the slower build). Anyone pulling:latestthen got LibreOffice without choosing the office image, and with the defaultFILEMORPH_OFFICE_ENGINE=auto, complex DOCX files went through it.The v1.1.0 run's logs have expired (HTTP 410), but its job timestamps show the office build-and-push step ending 12 s after the slim one's. So on 2026-06-01,
:latestwas most likely the office image for about an hour, until the next main build. Main builds were never affected, because the action addslatestonly for tag refs. Found by the security review of #180.How it was checked
Source at the pinned SHA
dc80280…(= v6.2.0).src/flavor.ts,src/meta.tsandsrc/tag.tsare byte-identical in v6.1.0, which built v1.1.0.The action's own code: I ran
dist/index.cjs(blob9edb5c8, hash-checked) locally on the workflow's inputs, against a localhost API stub and without credentials:v1.2.01.2.0, 1.2, latest, sha-…, latest1.2.0, 1.2, latest, sha-…v1.2.01.2.0-office, 1.2-office, office, sha-…-office, latest1.2.0-office, 1.2-office, office, sha-…-officev1.3.0-rc.1lateston base onlymainlatest, sha-…/office, sha-…-officeGuard:
test_only_the_slim_image_is_tagged_latestintests/test_supply_chain_hygiene.pydoes three things:latest=false. A looser parser passed#old, latest=falseand'latest=false,"latest=true"', because the action skips#lines and unquotes CSV fields.latest. As in the action, only a line starting with#is a comment, so an indented# type=raw,value=latestcounts as an entry.baseleg that the raw entry is meant for.I ran 26 mutations through both the guard and the real action:
${{ vars.X }}line, 1 swapped-out actionThere were 0 misses.
Reviews: code-reviewer and security-auditor both said ready. Their suggestions are folded in: the exact pin, the
base-leg check, the comment rule for tag lines, precise CHANGELOG wording and a shorter entry.The full suite passes (1489 on main 7c4a46c, 72 skipped on Windows). ruff, gitleaks and the scope guard are clean.
The docs (
docs/self-hosting.md,docs/formats.md,docs/patch-policy.md,docs/release-signing.md) already call:latestthe slim image. That is now true right after a release too, so no doc change was needed.Not verifiable before merge
docker.ymlpushes:latest/:office, so it was not dispatched on this branch.latest=falseunder "Processing flavor input". It pushes the same tags as before.docker buildx imagetools inspect ghcr.io/mrchenglen/filemorph:latestmust show the digest of:X.Y.Z, not:X.Y.Z-office.Out of scope (follow-up)
The raw entry has no
enable=condition. So anydocker.ymlrun on a ref other thanmainmoves:latest/:office: a pre-release tag, a backport tag, or a manual run on a branch. There is also noconcurrency:group. This is filed as a separate task.🤖 Generated with Claude Code