Skip to content

Scheduled monthly patch release #532

Closed

Description

@simonchrz

Follow-up to #514: a scheduled job that cuts a patch release on the 1st of each month if main is green and anything has merged since the last tag.

How it could work

A new monthly-release.yml on schedule (06:00 UTC on the 1st) plus workflow_dispatch with a dry_run input:

  1. Find the last v* tag and stop if nothing has merged since.
  2. Stop if any check on main's HEAD isn't finished and green.
  3. Stop if a PR labelled breaking was merged since the last tag. That needs a minor release by hand.
  4. Otherwise create the next patch tag through the API and start release.yml and publish.yml on it.

The catch: a tag created with GITHUB_TOKEN doesn't trigger other workflows, so pushing the tag alone wouldn't start the release. workflow_dispatch is exempt from that, and gh workflow run release.yml --ref v0.9.2 runs with github.ref set to the tag, so GoReleaser and docker/metadata-action behave as on a tag push. The only change to existing workflows would be adding workflow_dispatch to release.yml (publish.yml already has it). No extra secret needed. The alternative is pushing the tag with a GitHub App token, which leaves the workflows untouched but adds a secret.

Sketch
name: Monthly patch release

on:
  schedule:
    - cron: "0 6 1 * *"
  workflow_dispatch:
    inputs:
      dry_run:
        type: boolean
        default: true

permissions: {}

concurrency:
  group: monthly-release

jobs:
  release:
    runs-on: ubuntu-latest
    permissions:
      contents: write
      actions: write
      checks: read
    env:
      GH_TOKEN: ${{ github.token }}
      REPO: ${{ github.repository }}
      DRY_RUN: ${{ inputs.dry_run || 'false' }}
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        with:
          ref: main
          fetch-depth: 0
          persist-credentials: false

      - name: Decide whether to release
        id: plan
        run: |
          set -euo pipefail
          sha=$(git rev-parse HEAD)
          last=$(git describe --tags --abbrev=0 --match 'v*')
          if [ "$(git rev-list --count "$last..$sha")" -eq 0 ]; then
            echo "Nothing merged since $last"; exit 0
          fi
          bad=$(gh api "repos/$REPO/commits/$sha/check-runs?per_page=100" \
            --jq '[.check_runs[] | select(.status != "completed"
                   or (.conclusion | IN("success","skipped","neutral") | not))] | length')
          if [ "$bad" -ne 0 ]; then
            echo "::warning::main ($sha) is not green, skipping"; exit 0
          fi
          prs=$(gh pr list -R "$REPO" --state merged --base main \
            --search "merged:>$(git log -1 --format=%cI "$last") label:breaking" \
            --json number --jq 'length')
          if [ "$prs" -ne 0 ]; then
            echo "::warning::breaking change merged since $last, needs a manual minor release"; exit 0
          fi
          IFS=. read -r major minor patch <<<"${last#v}"
          echo "tag=v$major.$minor.$((patch + 1))" >> "$GITHUB_OUTPUT"
          echo "sha=$sha" >> "$GITHUB_OUTPUT"

      - name: Tag and start the release workflows
        if: steps.plan.outputs.tag != ''
        env:
          TAG: ${{ steps.plan.outputs.tag }}
          SHA: ${{ steps.plan.outputs.sha }}
        run: |
          set -euo pipefail
          echo "Releasing $TAG at $SHA (dry run: $DRY_RUN)"
          [ "$DRY_RUN" = "true" ] && exit 0
          gh api "repos/$REPO/git/refs" -f ref="refs/tags/$TAG" -f sha="$SHA"
          gh workflow run release.yml -R "$REPO" --ref "$TAG"
          gh workflow run publish.yml -R "$REPO" --ref "$TAG"

Open questions

  • Is a breaking label workable? If someone forgets it, a breaking change goes out as a patch. Make the Gradle build cache read-only by default #509 would have been one.
  • Should "green" mean every check on main, or only the ci.yml run?
  • Should a month with only Dependabot bumps get a release?
  • workflow_dispatch on release.yml or a GitHub App token?

If this sounds right I'll open a PR, starting in dry-run mode.

Activity

  1. added a commit that references this issue on Oct 24, 2026
    d4addfe
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions