From fbf0e19cbabac191831ebc5d2c5568b96c2e1cfe Mon Sep 17 00:00:00 2001 From: Chris Bargmann Date: Wed, 12 Aug 2026 11:02:51 +0200 Subject: [PATCH 1/2] feat: add reusable renovate-auto-approve workflow Reusable workflow_call workflow that approves Renovate PRs labeled automerge (and not security) via the gh CLI, and revokes its own prior approval if a PR later gains the security label. Ships an example caller under example/.github/workflows and a NEW_REPO onboarding note. The automerge policy (which update types are eligible) stays in the renovate-config presets; this repo hosts the workflow that acts on it, alongside the existing repo-settings governance. --- .github/workflows/renovate-auto-approve.yml | 75 +++++++++++++++++++ docs/NEW_REPO.md | 12 +++ .../workflows/renovate-auto-approve.yml | 15 ++++ 3 files changed, 102 insertions(+) create mode 100644 .github/workflows/renovate-auto-approve.yml create mode 100644 example/.github/workflows/renovate-auto-approve.yml diff --git a/.github/workflows/renovate-auto-approve.yml b/.github/workflows/renovate-auto-approve.yml new file mode 100644 index 0000000..d414b19 --- /dev/null +++ b/.github/workflows/renovate-auto-approve.yml @@ -0,0 +1,75 @@ +name: renovate-auto-approve + +# Reusable workflow. A consuming repo triggers it on pull_request and calls it: +# +# jobs: +# approve: +# uses: opendefensecloud/dev-kit/.github/workflows/renovate-auto-approve.yml@ +# permissions: +# pull-requests: write +# +# It approves a Renovate PR that carries the automerge label and not the block +# label (security). If a PR later gains the block label, it 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. + +on: + workflow_call: + inputs: + automerge-label: + description: Label that marks a Renovate PR as safe to auto-approve. + type: string + default: automerge + block-label: + description: Label that blocks auto-approval, and revokes an earlier one. + type: string + default: security + renovate-actor: + description: Login of the Renovate app that authors the pull requests. + type: string + default: renovate[bot] + +permissions: + pull-requests: write + +jobs: + auto-approve: + runs-on: ubuntu-latest + 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)) + steps: + - name: Approve, or revoke a prior approval if the block label appears + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + PR_NUMBER: ${{ github.event.pull_request.number }} + LABEL: ${{ inputs.automerge-label }} + BLOCK_LABEL: ${{ inputs.block-label }} + HAS_BLOCK: ${{ contains(github.event.pull_request.labels.*.name, inputs.block-label) }} + run: | + set -euo pipefail + # 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 [ "$latest_state" = "APPROVED" ]; then + echo "PR now carries the $BLOCK_LABEL label; revoking the earlier automated approval." + gh pr review "$PR_NUMBER" --request-changes \ + --body "Revoked: this PR is now labeled \`$BLOCK_LABEL\` and needs a human review." + else + echo "PR carries the $BLOCK_LABEL label; no automated approval to revoke." + fi + exit 0 + fi + if [ "$latest_state" = "APPROVED" ]; then + echo "Already approved by github-actions; nothing to do." + exit 0 + fi + gh pr review "$PR_NUMBER" --approve \ + --body "Auto-approved: Renovate \`$LABEL\` update. Merge remains gated on required status checks." diff --git a/docs/NEW_REPO.md b/docs/NEW_REPO.md index 951169c..c19a922 100644 --- a/docs/NEW_REPO.md +++ b/docs/NEW_REPO.md @@ -76,6 +76,17 @@ 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` and skips (and revokes) anything labeled `security`. + +Two more things are needed: + +- **Settings > Actions > General > Workflow permissions:** enable ["Allow GitHub Actions to create and approve pull requests"](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/enabling-features-for-your-repository/managing-github-actions-settings-for-a-repository#preventing-github-actions-from-creating-or-approving-pull-requests), otherwise the approval is rejected. +- The `protect-main` ruleset from `make repo-settings` already requires 1 approval and status checks. Put your CI/e2e checks in `REPO_STATUS_CHECKS` so the merge stays gated on them. + +Which update types are eligible is defined by the [renovate-config](https://github.com/opendefensecloud/renovate-config) presets. Major and security updates never get the `automerge` label, so they still need a human. + ## 6. Add the pull request template Copy `.github/pull_request_template.md` from this repository into your project. It provides a @@ -103,6 +114,7 @@ If Renovate is not enabled, check that the Renovate GitHub App is installed for - [ ] `make help` lists all available targets - [ ] `make repo-settings` ran successfully - [ ] GitHub workflows are in place and passing +- [ ] Renovate auto-approve workflow added and "Allow GitHub Actions to approve pull requests" enabled (if using automerge) - [ ] Renovate onboarding PR has been merged - [ ] Organization secrets are whitelisted for the repo - [ ] Private runners are whitelisted (if applicable) diff --git a/example/.github/workflows/renovate-auto-approve.yml b/example/.github/workflows/renovate-auto-approve.yml new file mode 100644 index 0000000..7c38c51 --- /dev/null +++ b/example/.github/workflows/renovate-auto-approve.yml @@ -0,0 +1,15 @@ +# Auto-approve Renovate digest/patch/minor PRs so they can automerge. +# Calls the reusable workflow in opendefensecloud/dev-kit. The automerge policy +# (which update types are eligible) comes from the renovate-config presets. +name: renovate-auto-approve +on: + pull_request: + types: [opened, reopened, synchronize, labeled] +permissions: + pull-requests: write +jobs: + approve: + # Pin @ to a dev-kit release tag or commit SHA before using this. + uses: opendefensecloud/dev-kit/.github/workflows/renovate-auto-approve.yml@ + permissions: + pull-requests: write From 20a32714d17234a195316d46ca817563d41e47e8 Mon Sep 17 00:00:00 2001 From: Chris Bargmann Date: Wed, 12 Aug 2026 11:12:57 +0200 Subject: [PATCH 2/2] fix: harden auto-approve against label races Re-read the PR labels immediately before approving, so an approval started from a stale trigger payload does not land after the security label was added concurrently. Serialize runs per PR with a concurrency group and cancel-in-progress. The example caller also triggers on unlabeled so removing the security label re-evaluates the PR. --- .github/workflows/renovate-auto-approve.yml | 10 ++++++++++ example/.github/workflows/renovate-auto-approve.yml | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/renovate-auto-approve.yml b/.github/workflows/renovate-auto-approve.yml index d414b19..9b50826 100644 --- a/.github/workflows/renovate-auto-approve.yml +++ b/.github/workflows/renovate-auto-approve.yml @@ -35,6 +35,10 @@ on: permissions: pull-requests: write +concurrency: + group: renovate-auto-approve-${{ github.event.pull_request.number }} + cancel-in-progress: true + jobs: auto-approve: runs-on: ubuntu-latest @@ -71,5 +75,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." + exit 0 + fi gh pr review "$PR_NUMBER" --approve \ --body "Auto-approved: Renovate \`$LABEL\` update. Merge remains gated on required status checks." diff --git a/example/.github/workflows/renovate-auto-approve.yml b/example/.github/workflows/renovate-auto-approve.yml index 7c38c51..9c762a4 100644 --- a/example/.github/workflows/renovate-auto-approve.yml +++ b/example/.github/workflows/renovate-auto-approve.yml @@ -4,7 +4,7 @@ name: renovate-auto-approve on: pull_request: - types: [opened, reopened, synchronize, labeled] + types: [opened, reopened, synchronize, labeled, unlabeled] permissions: pull-requests: write jobs: