From 291460b25391e51994fa53902482d6c349f4a38c Mon Sep 17 00:00:00 2001 From: Baptiste LAFOURCADE Date: Mon, 13 Jul 2026 05:01:42 +0200 Subject: [PATCH] ci(framework): close a milestone when its last issue closes A milestone stays open after all its issues are done, so its Friday due date reads as overdue and the roadmap view keeps showing the finished group. GitHub has no native rule for it. This closes any open milestone with at least one closed issue and zero open ones; an empty milestone is left alone. Runs on issue close and a weekly cron. Co-Authored-By: Claude Opus 4.8 --- .../workflows/close-finished-milestones.yml | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 .github/workflows/close-finished-milestones.yml diff --git a/.github/workflows/close-finished-milestones.yml b/.github/workflows/close-finished-milestones.yml new file mode 100644 index 00000000..692bfc72 --- /dev/null +++ b/.github/workflows/close-finished-milestones.yml @@ -0,0 +1,51 @@ +name: Close finished milestones + +# A milestone whose last issue just closed should close too, so its due date +# stops reading as "overdue" and the roadmap view drops the finished group. +# GitHub has no native rule for this. This closes any OPEN milestone that has +# at least one closed issue and zero open ones. An empty milestone (no issues +# at all) is left untouched — it is a freshly created bucket, not finished work. +# +# Runs when an issue closes (the moment a milestone can become finished) and on +# a weekly safety net for anything the event missed. Reopening a milestone by +# hand is safe: this only ever acts on milestones that have become empty of open +# issues, so it will not re-close one you deliberately reopened to add work. + +on: + issues: + types: [closed] + schedule: + - cron: "0 6 * * 1" # Mondays 06:00 UTC + workflow_dispatch: + +concurrency: + group: close-finished-milestones + cancel-in-progress: false + +permissions: + issues: write + +jobs: + close-finished-milestones: + name: Close milestones with no open issues + runs-on: ubuntu-latest + steps: + - name: Close finished milestones + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + run: | + set -euo pipefail + gh api --paginate "repos/$REPO/milestones?state=open&per_page=100" \ + --jq '.[] | select(.closed_issues > 0 and .open_issues == 0) | "\(.number)\t\(.title)"' \ + > finished.tsv || true + + if [ ! -s finished.tsv ]; then + echo "No finished milestone to close." + exit 0 + fi + + while IFS=$'\t' read -r number title; do + echo "Closing milestone #$number ($title)" + gh api -X PATCH "repos/$REPO/milestones/$number" -f state=closed >/dev/null + done < finished.tsv