ci(docker-publish): serialise publishes so two runs cannot race :latest - #106
Merged
Merged
Conversation
Three triggers feed this workflow -- tag push, cli-release dispatch and manual dispatch -- and nothing stopped them overlapping. On 2026-09-12 two dispatches for 1.3.6 ran at once (17:02:37 and 17:04:52) and one had to be cancelled by hand. Both would have pushed :X.Y.Z, :X.Y, :X and :latest. The last writer of :latest wins, so a slower run of an OLDER version can land on top of a newer one and silently become what every `docker pull nself/nself-admin` receives. #105 had just removed release.yml's ungated publisher for the same class of reason; this is the same problem one layer up, between two runs of the gated publisher itself. The group is a constant, not the version. Serialising per-version would still let 1.3.6 and 1.3.7 race, and the shared floating tags are exactly what they contend for. One image, one publisher. cancel-in-progress is false deliberately. Cancelling a publish mid-push is not a safe stop -- that is what happened to release.yml on v1.3.6, killed partway through pushing with the manifest unfinished. A queued run costs minutes; a half-pushed manifest is a broken :latest. Trade-off worth stating: GitHub keeps only one pending run per group, so a third request cancels the second while it waits. That is correct for the duplicate-dispatch case this fixes (same version twice -- drop the redundant one). For three distinct versions queued at once it would drop the middle; the Hygiene release-tag gate catches a version whose image never published, and releases here are sequential and rare. Pure addition: no existing key changed. Verified the workflow still parses with all three triggers and the docker-publish job intact.
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.
Three triggers feed this workflow — tag push,
cli-releasedispatch, manual dispatch — and nothing stopped them overlapping.This fired for real today. Two dispatches for 1.3.6 ran simultaneously (17:02:37 and 17:04:52); one was cancelled by hand. Both would have pushed
:X.Y.Z,:X.Y,:Xand:latest. The last writer of:latestwins, so a slower run of an older version can land on top of a newer one and silently become what everydocker pull nself/nself-adminreceives.#105 had just removed
release.yml's ungated publisher for the same class of reason. This is that problem one layer up — between two runs of the gated publisher itself.Two deliberate choices
The group is a constant, not the version. Serialising per-version would still let 1.3.6 and 1.3.7 race, and the shared floating tags are exactly what they contend for. One image, one publisher.
cancel-in-progress: false. Cancelling a publish mid-push is not a safe stop — that is what happened torelease.ymlon v1.3.6, killed partway through pushing with the manifest unfinished. A queued run costs minutes; a half-pushed manifest is a broken:latest.Trade-off, stated rather than hidden
GitHub keeps only one pending run per group, so a third request cancels the second while it waits. That is the right outcome for the duplicate-dispatch case this fixes (same version twice — drop the redundant one). For three distinct versions queued at once it would drop the middle one. The Hygiene release-tag gate catches a version whose image never published, and releases here are sequential and rare.
Verified: pure addition (28 insertions, 0 deletions, no existing key changed); workflow still parses with all three triggers and the
docker-publishjob intact.