-
Notifications
You must be signed in to change notification settings - Fork 125
Description
Summary
Add close-milestone jobs to both release workflows so that GitHub milestones are automatically closed when a release ships. Milestones currently remain open indefinitely after releases publish, requiring manual intervention. v3.1.0's milestone was discovered open with 46 items after v3.1.46 had already published.
Motivation
- Milestones remain open after releases ship, creating stale sprint tracking artifacts.
- Manual milestone closure is error-prone and easy to forget.
- Automating this eliminates a recurring housekeeping step from the sprint workflow.
Requirements
Stable Release Channel (main.yml)
Add a close-milestone job after the release-please job that:
- Runs when
release_created == 'true'. - Maps the release-please
versionoutput (e.g.,3.2.0) to milestone titlev3.2.0. - Closes the matching open milestone via
gh api. - Logs an informational message and exits cleanly if no matching milestone exists.
Placement: Parallel with reset-prerelease and other post-release jobs. No dependency on artifact packaging jobs.
Pre-Release Channel (prerelease-release.yml)
Add a close-milestone job after the prerelease-tag job that:
- Runs after
prerelease-tagsucceeds. - Extracts
MAJORandMINORfrom theversionoutput (e.g.,3.3.12) and constructs milestone titlev{MAJOR}.{MINOR}.0(e.g.,v3.3.0). - Closes the matching open milestone via
gh api. - Logs an informational message and exits cleanly if no matching milestone exists.
Placement: Parallel with extension-package-prerelease. No dependency on artifact packaging.
Pre-Release Idempotency
Pre-release milestones close on the first prerelease/next PR merge for that minor version. Subsequent pre-releases under the same minor are a no-op because the state=open filter excludes already-closed milestones.
Permissions and Security
- Both jobs use
${{ github.token }}(defaultGITHUB_TOKEN) — no new secrets or GitHub App tokens required. - Job-level
permissions: issues: writescoped to only the new jobs, following least-privilege conventions already used in both workflow files. - No changes to existing job permissions or dependencies.
Implementation Detail
Stable Release Job (main.yml)
close-milestone:
name: Close Release Milestone
needs: [release-please]
if: ${{ needs.release-please.outputs.release_created == 'true' }}
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- name: Close milestone for released version
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ needs.release-please.outputs.version }}
run: |
REPO="${{ github.repository }}"
TITLE="v${VERSION}"
NUMBER=$(gh api "/repos/$REPO/milestones?state=open&per_page=100" \
--jq ".[] | select(.title == \"$TITLE\") | .number")
if [ -n "$NUMBER" ]; then
gh api "/repos/$REPO/milestones/$NUMBER" \
-X PATCH -f state=closed
echo "✅ Closed milestone $TITLE (#$NUMBER)"
else
echo "ℹ️ No open milestone found matching '$TITLE' — skipping"
fiPre-Release Job (prerelease-release.yml)
close-milestone:
name: Close Pre-Release Milestone
needs: [prerelease-tag]
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- name: Close milestone for pre-release version
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ needs.prerelease-tag.outputs.version }}
run: |
REPO="${{ github.repository }}"
MAJOR=$(echo "$VERSION" | cut -d. -f1)
MINOR=$(echo "$VERSION" | cut -d. -f2)
TITLE="v${MAJOR}.${MINOR}.0"
NUMBER=$(gh api "/repos/$REPO/milestones?state=open&per_page=100" \
--jq ".[] | select(.title == \"$TITLE\") | .number")
if [ -n "$NUMBER" ]; then
gh api "/repos/$REPO/milestones/$NUMBER" \
-X PATCH -f state=closed
echo "✅ Closed milestone $TITLE (#$NUMBER)"
else
echo "ℹ️ No open milestone found matching '$TITLE' — skipping"
fiEdge Cases
| Scenario | Expected Behavior |
|---|---|
| Milestone does not exist for the released version | No-op with informational log message |
| Milestone already closed | state=open filter excludes it; no-op |
| Milestone has open issues/PRs remaining | Milestone closes; open items remain under the closed milestone |
| Multiple milestones with the same title | First match is closed (unlikely given naming convention) |
Version v4.0.0 with no preceding pre-release |
Stable job closes v4.0.0 only |
Pre-release v3.3.12 ships but v3.3.0 milestone absent |
No-op with log message |
per_page=100 pagination |
Safe for foreseeable future (current count ~6 milestones) |
Scope Boundaries
In scope:
- Milestone close automation for both release channels
Out of scope (potential follow-ups):
- Issue cascading: automatically moving open items from a closed milestone to the next open one
- Milestone creation automation: auto-creating the next milestone when one closes
- Milestone due date enforcement
Files Changed
| File | Change |
|---|---|
.github/workflows/main.yml |
Add close-milestone job (~18 lines) |
.github/workflows/prerelease-release.yml |
Add close-milestone job (~20 lines) |
No changes to prerelease.yml, release-please-config.json, .release-please-manifest.json, scripts, instructions, or documentation.
Acceptance Criteria
- A stable release of
v3.2.0closes thev3.2.0milestone if it exists - A pre-release publish of
v3.3.xcloses thev3.3.0milestone if it exists - No error when the target milestone does not exist
- No new secrets or App tokens required
- No changes to existing job dependencies or release artifact flows
- Job-level permissions scoped to
issues: writeonly