From 7ce895309507beace131f90a8f5ce7030b0cc1d6 Mon Sep 17 00:00:00 2001 From: Moritz Bracht Date: Tue, 20 Oct 2026 16:42:16 +0200 Subject: [PATCH 1/3] fix: revoke auto-approval when the automerge label is removed The job only ran while the PR carried the automerge or block label, so the run for removing automerge was skipped and an earlier approval stayed. The job now runs for every Renovate PR and rejects (revoking any earlier approval) when automerge is missing. Labels are read live via the API instead of from the event payload, since runs can finish out of event order. The final re-read before approving now also requires automerge. Closes #62 --- .github/workflows/renovate-auto-approve.yml | 35 ++++++++++++--------- docs/NEW_REPO.md | 2 +- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/.github/workflows/renovate-auto-approve.yml b/.github/workflows/renovate-auto-approve.yml index b67a881..a6b945d 100644 --- a/.github/workflows/renovate-auto-approve.yml +++ b/.github/workflows/renovate-auto-approve.yml @@ -25,11 +25,11 @@ name: renovate-auto-approve # It approves a Renovate PR that carries the automerge label, added by Renovate, # and not the block label (security), and only while the branch still holds # exactly one commit, authored by Renovate and signed, and pushed by Renovate. -# Once someone pushes -# onto the branch it stops qualifying, because that change was never reviewed by -# anyone. If a PR stops qualifying or later gains the block label, the workflow -# revokes its own earlier approval so a human is required again. The merge -# itself always stays gated on the repo's required status checks. +# Once someone pushes onto the branch it stops qualifying, because that change +# was never reviewed by anyone. If a PR stops qualifying (including losing the +# automerge label) or later gains the block label, the workflow revokes its own +# earlier approval so a human is required again. The merge itself always stays +# gated on the repo's required status checks. # # Which update types are eligible (the automerge label) is decided by the # renovate-config presets, not here: github>opendefensecloud/renovate-config. @@ -61,11 +61,11 @@ concurrency: jobs: auto-approve: runs-on: ubuntu-26.04 + # No label condition: a PR that just lost the automerge label must still + # reach the revoke path below. if: >- github.event_name == 'pull_request' && - github.event.pull_request.user.login == inputs.renovate-actor && - (contains(github.event.pull_request.labels.*.name, inputs.automerge-label) || - contains(github.event.pull_request.labels.*.name, inputs.block-label)) + github.event.pull_request.user.login == inputs.renovate-actor steps: - name: Approve an unmodified Renovate PR, or revoke a prior approval env: @@ -75,14 +75,17 @@ jobs: HEAD_REF: ${{ github.event.pull_request.head.ref }} LABEL: ${{ inputs.automerge-label }} BLOCK_LABEL: ${{ inputs.block-label }} - HAS_BLOCK: ${{ contains(github.event.pull_request.labels.*.name, inputs.block-label) }} RENOVATE_ACTOR: ${{ inputs.renovate-actor }} run: | set -euo pipefail + # Current labels, not the event payload's: runs can finish out of + # event order, and a stale payload would approve or revoke wrongly. + labels=$(gh pr view "$PR_NUMBER" --json labels --jq '.labels[].name') + has_label() { grep -qxF "$1" <<<"$labels"; } # Latest review state left by this workflow's identity (github-actions[bot]). latest_state=$(gh api "repos/$GH_REPO/pulls/$PR_NUMBER/reviews" \ --jq 'map(select((.user.login // "" | ascii_downcase) | startswith("github-actions"))) | last | .state // empty') - if [ "$HAS_BLOCK" = "true" ]; then + if has_label "$BLOCK_LABEL"; then if [ "$latest_state" = "APPROVED" ]; then echo "PR now carries the $BLOCK_LABEL label; revoking the earlier automated approval." gh pr review "$PR_NUMBER" --request-changes \ @@ -103,6 +106,9 @@ jobs: fi exit 0 } + if ! has_label "$LABEL"; then + reject "the PR does not carry the $LABEL label" + fi # The job-level gate only checks who opened the PR. Also check that # the branch holds exactly one commit, authored by Renovate, signed, and # pushed by Renovate, and that Renovate itself added the automerge label. @@ -136,10 +142,11 @@ jobs: echo "Already approved by github-actions; nothing to do." exit 0 fi - # Re-read labels right before approving: the block label may have been - # added after this run started, concurrently with this approval. - if gh pr view "$PR_NUMBER" --json labels --jq '.labels[].name' | grep -qxF "$BLOCK_LABEL"; then - echo "PR now carries the $BLOCK_LABEL label; not approving." + # Re-read labels right before approving: they may have changed after + # this run started, concurrently with this approval. + labels=$(gh pr view "$PR_NUMBER" --json labels --jq '.labels[].name') + if has_label "$BLOCK_LABEL" || ! has_label "$LABEL"; then + echo "Labels changed while this run was checking; not approving." exit 0 fi gh pr review "$PR_NUMBER" --approve \ diff --git a/docs/NEW_REPO.md b/docs/NEW_REPO.md index 6b606a3..9ef1492 100644 --- a/docs/NEW_REPO.md +++ b/docs/NEW_REPO.md @@ -104,7 +104,7 @@ preCommitHooks = { ### Renovate auto-approve (optional) -To let Renovate auto-merge digest, patch, and minor PRs without a manual approval, copy `example/.github/workflows/renovate-auto-approve.yml` into your project and pin the `uses:` ref to a dev-kit release tag or commit SHA. It calls dev-kit's reusable `renovate-auto-approve.yml`, which approves Renovate PRs labeled `automerge` by Renovate itself and skips (and revokes) anything labeled `security`. It approves a PR only while the branch is still exactly one signed Renovate commit that Renovate itself pushed, so pushing onto a Renovate branch, including amending or rebasing its commit, revokes the automated approval and hands the review back to a human. +To let Renovate auto-merge digest, patch, and minor PRs without a manual approval, copy `example/.github/workflows/renovate-auto-approve.yml` into your project and pin the `uses:` ref to a dev-kit release tag or commit SHA. It calls dev-kit's reusable `renovate-auto-approve.yml`, which approves Renovate PRs labeled `automerge` by Renovate itself and skips (and revokes) anything labeled `security`. It approves a PR only while the branch is still exactly one signed Renovate commit that Renovate itself pushed, so pushing onto a Renovate branch, including amending or rebasing its commit, revokes the automated approval and hands the review back to a human. Removing the `automerge` label revokes it the same way. Two more things are needed: From d3b41aa5fa992ef057d0ceaf3071d6b0ff07d982 Mon Sep 17 00:00:00 2001 From: Moritz Bracht Date: Tue, 20 Oct 2026 16:59:04 +0200 Subject: [PATCH 2/3] fix: skip closed PRs in renovate-auto-approve Without the label gate, removing automerge from a merged or closed PR started the job and could post a request-changes review on it. --- .github/workflows/renovate-auto-approve.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/renovate-auto-approve.yml b/.github/workflows/renovate-auto-approve.yml index a6b945d..d5cf56d 100644 --- a/.github/workflows/renovate-auto-approve.yml +++ b/.github/workflows/renovate-auto-approve.yml @@ -65,6 +65,7 @@ jobs: # reach the revoke path below. if: >- github.event_name == 'pull_request' && + github.event.pull_request.state == 'open' && github.event.pull_request.user.login == inputs.renovate-actor steps: - name: Approve an unmodified Renovate PR, or revoke a prior approval From 689be687fc18c20d84bd8c70e6d723abd67eb694 Mon Sep 17 00:00:00 2001 From: Moritz Bracht Date: Wed, 21 Oct 2026 12:56:00 +0200 Subject: [PATCH 3/3] fix: revoke when labels change during an auto-approve run The final label re-read ran after the already-approved exit, so a run that saw automerge removed or security added mid-run left an earlier approval in place. Re-read before that exit and revoke through reject. --- .github/workflows/renovate-auto-approve.yml | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/.github/workflows/renovate-auto-approve.yml b/.github/workflows/renovate-auto-approve.yml index d5cf56d..0414fff 100644 --- a/.github/workflows/renovate-auto-approve.yml +++ b/.github/workflows/renovate-auto-approve.yml @@ -139,15 +139,13 @@ jobs: if [ "$labeler" != "$RENOVATE_ACTOR" ]; then reject "the $LABEL label was added by '$labeler', not $RENOVATE_ACTOR" fi - if [ "$latest_state" = "APPROVED" ]; then - echo "Already approved by github-actions; nothing to do." - exit 0 - fi - # Re-read labels right before approving: they may have changed after - # this run started, concurrently with this approval. + # Re-read labels: they may have changed after this run started. labels=$(gh pr view "$PR_NUMBER" --json labels --jq '.labels[].name') if has_label "$BLOCK_LABEL" || ! has_label "$LABEL"; then - echo "Labels changed while this run was checking; not approving." + reject "the labels changed while this run was checking" + fi + if [ "$latest_state" = "APPROVED" ]; then + echo "Already approved by github-actions; nothing to do." exit 0 fi gh pr review "$PR_NUMBER" --approve \