Skip to content

Commit 291b052

Browse files
committed
improvement(ci): promote superseded first-attempt runs forward
The stale-promotion guard skipped any run whose commit was no longer the branch head. If commit A passed its gate but was superseded mid-run by commit B, and B then failed tests, A's promotion was skipped and the deploy tags stayed on pre-A code with no automatic recovery (Cursor finding on #5712). A first-attempt run promoting an ancestor of the branch head is always a forward deploy — runs on a ref are serialized by the concurrency group, so nothing newer can have promoted first. Only re-runs of superseded commits (a rollback attempt) and force-pushed-away commits are skipped. Same semantics applied to the GHCR latest-tag guard.
1 parent 8adeaab commit 291b052

1 file changed

Lines changed: 42 additions & 10 deletions

File tree

.github/workflows/ci.yml

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -300,17 +300,34 @@ jobs:
300300

301301
# Re-running this job from an old run would retag the deploy tags back
302302
# to that run's commit and immediately trigger a production deploy of
303-
# stale code. Only promote while this commit is still the branch head;
304-
# when superseded, skip cleanly — the newer run's promotion covers it.
303+
# stale code. A first-attempt run whose commit is an ancestor of the
304+
# branch head is still a forward deploy (runs on a ref are serialized
305+
# by the concurrency group, so nothing newer can have promoted first) —
306+
# it promotes even when superseded, so a failing successor can't strand
307+
# a passing commit. Only re-runs of superseded commits (a rollback
308+
# attempt) and force-pushed-away commits are skipped.
305309
- name: Guard against stale promotion
306310
id: guard
311+
env:
312+
GH_TOKEN: ${{ github.token }}
307313
run: |
308314
HEAD_SHA="$(git ls-remote "https://github.com/${{ github.repository }}.git" "refs/heads/${GITHUB_REF_NAME}" | cut -f1)"
309-
if [ "$HEAD_SHA" != "${{ github.sha }}" ]; then
310-
echo "::warning::Skipping promotion of ${{ github.sha }}: ${GITHUB_REF_NAME} has moved to ${HEAD_SHA}. Moving the deploy tags to this commit would deploy stale code; push a revert commit to roll back instead."
315+
if [ "$HEAD_SHA" = "${{ github.sha }}" ]; then
316+
echo "fresh=true" >> $GITHUB_OUTPUT
317+
exit 0
318+
fi
319+
if [ "${{ github.run_attempt }}" != "1" ]; then
320+
echo "::warning::Skipping promotion of ${{ github.sha }} (re-run): ${GITHUB_REF_NAME} has moved to ${HEAD_SHA}. Moving the deploy tags back would deploy stale code; push a revert commit to roll back instead."
311321
echo "fresh=false" >> $GITHUB_OUTPUT
312-
else
322+
exit 0
323+
fi
324+
STATUS="$(gh api "repos/${{ github.repository }}/compare/${{ github.sha }}...${HEAD_SHA}" --jq '.status' || echo "unknown")"
325+
if [ "$STATUS" = "ahead" ]; then
326+
echo "::notice::${GITHUB_REF_NAME} has moved to ${HEAD_SHA}, but ${{ github.sha }} is its ancestor — promoting forward."
313327
echo "fresh=true" >> $GITHUB_OUTPUT
328+
else
329+
echo "::warning::Skipping promotion of ${{ github.sha }}: ${GITHUB_REF_NAME} has moved to ${HEAD_SHA} (compare status: ${STATUS})."
330+
echo "fresh=false" >> $GITHUB_OUTPUT
314331
fi
315332
316333
- name: Promote images to deploy tags
@@ -420,17 +437,32 @@ jobs:
420437
password: ${{ secrets.GITHUB_TOKEN }}
421438

422439
# Same protection as promote-images: a re-run of an old run must not
423-
# move the public latest tags back to a stale commit. Immutable tags
424-
# (sha and version) are still published — only latest moves are gated.
440+
# move the public latest tags back to a stale commit, but a superseded
441+
# first-attempt run whose commit is an ancestor of head still publishes
442+
# forward. Immutable tags (sha and version) are always published — only
443+
# latest moves are gated.
425444
- name: Guard against stale latest tags
426445
id: guard
446+
env:
447+
GH_TOKEN: ${{ github.token }}
427448
run: |
428449
HEAD_SHA="$(git ls-remote "https://github.com/${{ github.repository }}.git" "refs/heads/${GITHUB_REF_NAME}" | cut -f1)"
429-
if [ "$HEAD_SHA" != "${{ github.sha }}" ]; then
430-
echo "::warning::${GITHUB_REF_NAME} has moved to ${HEAD_SHA}; publishing immutable tags for ${{ github.sha }} but skipping the latest tags."
450+
if [ "$HEAD_SHA" = "${{ github.sha }}" ]; then
451+
echo "fresh=true" >> $GITHUB_OUTPUT
452+
exit 0
453+
fi
454+
if [ "${{ github.run_attempt }}" != "1" ]; then
455+
echo "::warning::${GITHUB_REF_NAME} has moved to ${HEAD_SHA} and this is a re-run; publishing immutable tags for ${{ github.sha }} but skipping the latest tags."
431456
echo "fresh=false" >> $GITHUB_OUTPUT
432-
else
457+
exit 0
458+
fi
459+
STATUS="$(gh api "repos/${{ github.repository }}/compare/${{ github.sha }}...${HEAD_SHA}" --jq '.status' || echo "unknown")"
460+
if [ "$STATUS" = "ahead" ]; then
461+
echo "::notice::${GITHUB_REF_NAME} has moved to ${HEAD_SHA}, but ${{ github.sha }} is its ancestor — publishing latest tags forward."
433462
echo "fresh=true" >> $GITHUB_OUTPUT
463+
else
464+
echo "::warning::${GITHUB_REF_NAME} has moved to ${HEAD_SHA} (compare status: ${STATUS}); publishing immutable tags for ${{ github.sha }} but skipping the latest tags."
465+
echo "fresh=false" >> $GITHUB_OUTPUT
434466
fi
435467
436468
- name: Publish tags and manifests

0 commit comments

Comments
 (0)