@@ -252,6 +252,15 @@ jobs:
252252 if : ${{ steps.ctx.outputs.mode == 'main' && steps.affected.outputs.count == '0' }}
253253 run : echo "No affected release projects on main; skipping version + publish."
254254
255+ # The checkout above is pinned to the SHA resolved at dispatch time; by now (npm ci + setup)
256+ # the branch may have moved (e.g. a push-triggered next release), which makes the later
257+ # release-commit push non-fast-forward. Re-sync to the branch tip before versioning.
258+ - name : Sync to latest branch tip (dispatch)
259+ if : ${{ steps.ctx.outputs.mode == 'dispatch' && !inputs.dry-run }}
260+ run : |
261+ git fetch origin "$GITHUB_REF_NAME"
262+ git reset --hard "origin/$GITHUB_REF_NAME"
263+
255264 # Orchestrated release: bumps versions, generates changelogs, commits + tags in one shot.
256265 # The orchestrator does not accept --git-* flags (config-driven only); push is handled in the next step.
257266 # --skip-publish keeps publishing as a separate step below so the OIDC token-clearing logic still runs.
@@ -280,9 +289,11 @@ jobs:
280289 --skip-publish \
281290 --verbose
282291
292+ # --atomic: if the branch push is rejected, the release tags are rejected with it,
293+ # so a failed run leaves no orphaned tag behind and a plain re-dispatch is enough to retry.
283294 - name : Push release commit and tags (dispatch)
284295 if : ${{ steps.ctx.outputs.mode == 'dispatch' && !inputs.dry-run }}
285- run : git push --follow-tags
296+ run : git push --atomic -- follow-tags origin HEAD
286297
287298 - name : nx release version + changelog (dispatch, dry-run)
288299 if : ${{ steps.ctx.outputs.mode == 'dispatch' && inputs.dry-run }}
0 commit comments