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.
This commit is contained in:
Jinjing
2026-08-08 15:41:44 -07:00
parent 5c014cc04c
commit 5f6cdb7e61
+19 -4
View File
@@ -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"