feat: add a version-pr-reaper reusable - #69
Conversation
An auto-increment PR goes stale when a release lands by another route, or when a later bump overtakes a pending one. Nothing closed those, so they accumulated along with their branches. The selection logic belongs here rather than in each package: it is the same semver comparison, Project.toml-only check and merge-base orphan sweep everywhere, and a copy per package means a fix has to be re-synced across the org. The caller supplies the triggers and passes `dry_run` through. The reusable does not second-guess the caller's event: a caller that wants a preview from any trigger gets one. Co-authored-by: Sam Abbott <contact@samabbott.co.uk>
There was a problem hiding this comment.
Adds a reusable workflow that closes stale auto-increment version PRs and sweeps orphaned auto-increment branches once main's version catches up. The merge-base/ancestor logic and squash-merge caveat are handled correctly, but the PR-listing step can silently under-scan on a repo with many open PRs, which then lets the orphan sweep delete a branch still backing an open PR.
Automated first pass by seabbs-review-bot (Claude sonnet), triggered by: first pass. Not a human review. Comment @seabbs-review-bot to ask for another pass: @seabbs any time, the author's agent once it has pushed changes. Add the no-review label to opt this PR out. Ping @seabbs with any questions.
| : > handled_branches.txt | ||
|
|
||
| # Only the exact prefix the increment-version action writes. | ||
| PRS=$(gh pr list --state open --json number,headRefName \ |
There was a problem hiding this comment.
issue gh pr list defaults to --limit 30 for the whole open-PR list before the jq filter for the auto/version-increment- prefix runs. On a repo with more than 30 open PRs (plausible on a first run, since accumulated stale auto-increment PRs are exactly what this workflow exists to clear), older matching PRs beyond page 1 are silently skipped. Such a PR is then absent from handled_branches.txt, so the orphan-branch step below has no way to know it isn't orphaned and can delete its branch while the PR is still open. Add --limit 200 (or paginate) to the gh pr list call.
|
|
||
| # A single sortable integer so 0.10.0 compares above 0.9.0, | ||
| # which plain string or float comparison gets wrong. | ||
| semver_key() { |
There was a problem hiding this comment.
suggestion semver_key and CURRENT_KEY=$(semver_key "$CURRENT_VERSION") are duplicated verbatim in the orphan-branch step at lines 153-160. Both steps already read CURRENT_VERSION from steps.current, so compute CURRENT_KEY once in the "Read current version" step and expose it as an extra output for both later steps to consume, instead of re-deriving it twice.
Upstream half of EpiAware/EpiAwarePackageTools.jl#476, which @seabbs asked to be delivered as a managed action rather than copied into every package.
Why here
An auto-increment PR goes stale when a release lands by another route, or when a later bump overtakes a pending one. Nothing closed those, so PRs and their branches accumulated — six stale branches in the kit alone at the time of writing.
The kit's PR originally added ~248 lines of inline bash to the package and to its template, so every adopting package would carry a private copy of the selection logic and a fix would mean re-syncing ten repos. The logic is identical everywhere: the same semver comparison, the same Project.toml-only safety check, the same merge-base orphan sweep. It belongs in one place.
Once this lands, the kit side reduces to a thin
uses:caller plus scaffold wiring, which is also where EpiAware/EpiAwarePackageTools.jl#467 is taking the caller pins.What it does
Two passes, both guarded:
Project.tomlalone — so a PR someone has built on is never discarded.Two deliberate changes from the kit's version
dry_runis a plain input, not second-guessed. The kit's copy computed it asgithub.event_name == 'workflow_dispatch' && inputs.dry_run || 'false'. In a reusable that is a trap: a caller passingdry_run: truefrom any other trigger would have it silently ignored and reap for real. The caller decides; this workflow honours what it is given.Permissions are declared on the job (
contents: write,pull-requests: write) rather than inherited from a top-level block, matching how the other reusables here scope them.Both
gh pr closeandgit push --deleteabsorb their own failures, so one PR that a human already closed cannot abandon the rest of the run underset -euo pipefail.yaml.safe_loadparses it, and every other workflow in the repo still parses.This was opened by a bot. Please ping @seabbs for any questions.