From 16b6a545a5dfed8433464e8705c86f49fd97ab86 Mon Sep 17 00:00:00 2001 From: ajianaz Date: Thu, 15 Oct 2026 12:58:13 +0700 Subject: [PATCH] ci(release): close reminders for superseded releases (#1327) --- scripts/release-signature-reminder.sh | 38 +++++++++++++ .../tests/release-signature-reminder.test.sh | 53 +++++++++++++++++++ 2 files changed, 91 insertions(+) diff --git a/scripts/release-signature-reminder.sh b/scripts/release-signature-reminder.sh index 1151d6f9..c39ff16c 100755 --- a/scripts/release-signature-reminder.sh +++ b/scripts/release-signature-reminder.sh @@ -74,6 +74,44 @@ list_reminders() { } ISSUES="$(list_reminders)" +# Stale pass: reminders for releases that are no longer the latest. At most 20 +# per run; only titles matching the pattern exactly are ever touched. +# The jq program is kept in a variable (a quoted here-document) so it reads as the +# jq program it is; it only ever receives the issue list as JSON input. +STALE_FILTER="$(cat <<'JQ' +[.[] | select(.title + | test("^Sign release v[0-9]+\\.[0-9]+\\.[0-9]+ \\(minisign\\)$")) + | {number, tag: (.title | capture("^Sign release (?[^ ]+) \\(minisign\\)$").t)} + | select(.tag != $latest)] +| sort_by(.number) | .[0:20][] +| "\(.number) \(.tag)" +JQ +)" +STALE="$(printf '%s' "$ISSUES" | jq -r --arg latest "$TAG" "$STALE_FILTER")" +if [ -n "$STALE" ]; then + while read -r SNUM STAG; do + printf '%s' "$STAG" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+$' || continue + ERR="$(mktemp)" + if OLD="$(gh api "repos/$REPO/releases/tags/$STAG" 2>"$ERR" /dev/null; then + MSG="Signature found on the release, closing." + else + MSG="Superseded: $TAG is now the latest release, and \`uteke upgrade\` only installs the latest release, so this release no longer needs signing. Closing." + fi + elif grep -q 'HTTP 404' "$ERR"; then + MSG="Superseded: $TAG is now the latest release, and \`uteke upgrade\` only installs the latest release, so this release no longer needs signing. Closing." + else + echo "Could not look up $STAG for reminder #$SNUM; leaving it open." + rm -f "$ERR"; continue + fi + rm -f "$ERR" + echo "Reminder #$SNUM is for $STAG, not the latest ($TAG); closing." + mutate issue close "$SNUM" --repo "$REPO" --comment "$MSG" + done </dev/null } diff --git a/scripts/tests/release-signature-reminder.test.sh b/scripts/tests/release-signature-reminder.test.sh index a1cd5319..c143b27a 100644 --- a/scripts/tests/release-signature-reminder.test.sh +++ b/scripts/tests/release-signature-reminder.test.sh @@ -21,6 +21,10 @@ case "$1" in api) case "$2" in */releases/latest) printf '%s' "$STUB_RELEASE" ;; + */releases/tags/*) + f="$STUB_TAGDIR/${2##*/}.json" + if [ -n "${STUB_API_ERR-}" ]; then echo "$STUB_API_ERR" >&2; exit 1; fi + if [ -f "$f" ]; then cat "$f"; else echo "gh: Not Found (HTTP 404)" >&2; exit 1; fi ;; esac ;; issue) case "$2" in @@ -158,15 +162,56 @@ t "unknown flag before --dry-run -> exit 2" rc_is 2 run "$(rel v1.2.3 "$A_BUNDLE")" '[]' --dry-run --dry-run t "repeated --dry-run is harmless" no_mutation +# --- stale pass: reminders for superseded releases --- +rm -f "$T/tags/"* +echo "{\"assets\":$A_SIGNED}" > "$T/tags/v1.0.0.json" +echo "{\"assets\":$A_BUNDLE}" > "$T/tags/v1.1.0.json" +LATEST="$(rel v1.2.3 "$A_SIGNED")" + +run "$LATEST" "[$(oldissue 3 v1.0.0)]" +t "old reminder, signed old release -> exit 0" rc_is 0 +grep -q '^CLOSE issue close 3 .*Signature found on the release, closing' "$T/log"; check "old signed -> Signature found comment" $? +grep -q '^CLOSE issue close 3 ' "$T/log"; check "old signed -> closed" $? + +run "$LATEST" "[$(oldissue 4 v1.1.0)]" +grep -q '^CLOSE issue close 4 .*Superseded: v1.2.3 is now the latest release' "$T/log"; check "old unsigned -> superseded comment" $? +grep -q '^CLOSE issue close 4 ' "$T/log"; check "old unsigned -> closed" $? + +run "$LATEST" "[$(oldissue 7 v1.2.3 "")]" +t "latest-tag reminder (signed latest) handled only by main logic: one close" test "$(nlog '^CLOSE ')" = 1 +grep -q 'Signature found' "$T/log"; check "latest-tag reminder gets the normal close, not superseded" $? +run "$(rel v1.2.3 "$A_BUNDLE")" "[$(oldissue 7 v1.2.3 "" app/github-actions 2)]" +t "latest-tag unsigned young reminder untouched by stale pass" no_mutation + +run "$LATEST" "[$(oldissue 5 v1.0.0 'Sign release v1.0.0 (minisign) please'),$(oldissue 6 v1.0.0 'Sign release v1.0.0-rc.1 (minisign)'),$(oldissue 9 x 'Unrelated issue')]" +t "non-matching titles never touched" no_mutation + +run "$LATEST" "[$(oldissue 3 v1.0.0),$(oldissue 4 v1.1.0)]" +t "two stale issues -> two closes" test "$(nlog '^CLOSE ')" = 2 +t "two stale issues -> comments ride on the closes" test "$(nlog '^COMMENT ')" = 0 + +run "$LATEST" "[$(oldissue 8 v0.9.0)]" +grep -q '^CLOSE issue close 8 .*Superseded' "$T/log"; check "404 on old tag -> superseded" $? +grep -q '^CLOSE issue close 8 ' "$T/log"; check "404 on old tag -> closed" $? + +run "$LATEST" "[$(oldissue 3 v1.0.0)]" --dry-run +t "--dry-run stale pass -> no mutating call" no_mutation + # --- trust boundary: issue authors --- BUN="$(rel v1.2.3 "$A_BUNDLE")" run "$BUN" "[$(oldissue 20 v1.2.3 "" stranger 2)]" t "stranger look-alike ignored, genuine issue still created" test "$(nlog '^CREATE ')" = 1 t "stranger look-alike never commented or closed" test "$(nlog '^COMMENT \|^CLOSE ')" = 0 +run "$LATEST" "[$(oldissue 21 v1.2.3 "" stranger 2),$(oldissue 22 v1.0.0 "" stranger)]" +t "stranger look-alikes (latest and stale) never touched when signed" no_mutation run "$BUN" "[$(oldissue 23 v1.2.3 "" 'github-actions[bot]' 13)]" grep -q '^COMMENT issue comment 23 ' "$T/log"; check "github-actions[bot] issue is acted on" $? run "$BUN" "[$(oldissue 24 v1.2.3 "" ajianaz 13)]" grep -q '^COMMENT issue comment 24 ' "$T/log"; check "RELEASE_SIGNER issue is acted on" $? +run "$LATEST" "[$(oldissue 25 v1.0.0 "" ajianaz)]" +grep -q '^CLOSE issue close 25 ' "$T/log"; check "signer-authored stale issue closed" $? +run "$LATEST" "[$(oldissue 26 v1.0.0 "" stranger)]" +t "stranger stale look-alike not closed" no_mutation # --- review hardening --- # listing never uses --search (stub fails the run if it does) @@ -190,6 +235,14 @@ t "REMINDER_AFTER_HOURS=08 accepted, 10h old -> re-pings" test "$RC" = 0 -a "$(n OUT="$(env PATH="$T/bin:$PATH" REMINDER_AFTER_HOURS=abc GH_REPO=o/r bash "$SCRIPT" 2>&1)"; RC=$? t "non-numeric hours -> exit 2" rc_is 2 +# 404 parsing +STUB_API_ERR="gh: Internal error mentions 404 (HTTP 502)" run "$LATEST" "[$(oldissue 3 v1.0.0)]" +t "5xx whose text contains 404 -> issue left open" no_mutation +STUB_API_ERR="gh: Server Error (HTTP 500)" run "$LATEST" "[$(oldissue 3 v1.0.0)]" +t "HTTP 500 on old tag -> issue left open" no_mutation +STUB_API_ERR="gh: API rate limit exceeded (HTTP 403)" run "$LATEST" "[$(oldissue 3 v1.0.0)]" +t "rate limit on old tag -> issue left open" no_mutation + # assign failure: re-list before retrying STUB_FAIL_ASSIGN=1 STUB_ISSUES2="[$(oldissue 30 v1.2.3 "" app/github-actions 1)]" run "$BUN" '[]' t "assign fails but issue now exists -> no second create" test "$(nlog '^CREATE ')" = 1