From 5f6cdb7e61dad5d4ec3796dc62db1a7b2ff0d98c Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Sat, 8 Aug 2026 15:41:44 -0700 Subject: [PATCH] fix(ci): harden hourly prune protect without retain+1 drift Review found that skipping only in the delete loop under-prunes when sort is wrong, and excluding TAG before the retain slice would permanently keep retain+1 releases. Force this run's tag to the front of the sorted list before slicing so it always has a retain seat and oldest builds still prune. Also gate prune on publish_live success and warn if TAG still appears stale. --- .github/workflows/hourly-mac-build.yml | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/.github/workflows/hourly-mac-build.yml b/.github/workflows/hourly-mac-build.yml index 36e4937971f..7d4ecc44131 100644 --- a/.github/workflows/hourly-mac-build.yml +++ b/.github/workflows/hourly-mac-build.yml @@ -380,7 +380,10 @@ jobs: echo "::warning::Could not discard draft $TAG; remove it manually." - name: Prune old hourly releases - if: steps.freshness.outputs.should_build == 'true' + # Only after a live publish: $TAG is then a non-draft we must not delete, + # and failed runs should not reshuffle retention around a draft that the + # failure path is about to discard. + if: steps.publish_live.outcome == 'success' shell: bash env: GH_TOKEN: ${{ steps.app_token_publish.outputs.token }} @@ -398,17 +401,29 @@ jobs: # createdAt (bulk import / re-create), so createdAt ranking is unstable: # reverse of a stable sort then drops the just-published tag past the # retain window. publishedAt is real recency; tagName (...YYYYMMDDHHMM) - # is the deterministic tie-break. Never prune $TAG from this run. + # is the deterministic tie-break. + # + # Why force $TAG to the front before slicing: a hard retain-window seat + # for this run's release. Dropping $TAG from the list *before* the slice + # would permanently keep retain+1 releases; skipping it only in the + # delete loop would under-prune when the sort is still wrong. Partition + # keeps relative order of every other tag. + jq_filter='map(select(.isDraft | not)) | sort_by(.publishedAt // "", .tagName) | reverse' + if [[ -n "${TAG:-}" ]]; then + jq_filter+=" | (map(select(.tagName == \"${TAG//\"/\\\"}\")) + map(select(.tagName != \"${TAG//\"/\\\"}\")))" + fi + jq_filter+=" | .[${HOURLY_RETAIN_COUNT}:] | .[].tagName" stale="$(gh release list --repo "$HOURLY_REPO" --limit 200 --json tagName,publishedAt,isDraft \ - --jq "map(select(.isDraft | not)) | sort_by(.publishedAt // \"\", .tagName) | reverse | .[${HOURLY_RETAIN_COUNT}:] | .[].tagName")" + --jq "$jq_filter")" if [[ -z "$stale" ]]; then echo "Nothing to prune; at or under $HOURLY_RETAIN_COUNT retained builds." exit 0 fi while read -r tag; do [[ -n "$tag" ]] || continue + # Belt-and-suspenders: partition above should already exclude $TAG. if [[ -n "${TAG:-}" && "$tag" == "$TAG" ]]; then - echo "Skipping just-published $tag" + echo "::warning::Prune list still included just-published $tag after protect; skipping delete." continue fi echo "Pruning $tag"