From 8ea86b6c64ff4e5f420d93690bf7ca0f4ad5ca03 Mon Sep 17 00:00:00 2001 From: Javier Lougedo Date: Wed, 9 Sep 2026 14:27:45 +0200 Subject: [PATCH 1/4] ci(NOJIRA-1234): Extend bot automerge to smartling and aikido Gate on the PR author login instead of a single actor, so translation PRs from smartling-github-connector[bot] and security fixes from aikido-autofix[bot] are auto-approved and auto-merged alongside dependabot. Titles are not a reliable signal for these bots, so the allowlist keys off github.actor only. Co-Authored-By: Claude Opus 5 --- .github/workflows/dependabot-automerge.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/dependabot-automerge.yml b/.github/workflows/dependabot-automerge.yml index bd569ba3..f433140c 100644 --- a/.github/workflows/dependabot-automerge.yml +++ b/.github/workflows/dependabot-automerge.yml @@ -1,4 +1,4 @@ -name: Dependabot auto-approve and auto-merge +name: Bot PR auto-approve and auto-merge on: pull_request_target: @@ -9,9 +9,10 @@ permissions: contents: write jobs: - dependabot: + automerge: runs-on: ubuntu-latest - if: github.actor == 'dependabot[bot]' + # Trusted automation only. Keep this list in sync across blocks-owned repos. + if: contains(fromJSON('["dependabot[bot]", "smartling-github-connector[bot]", "aikido-autofix[bot]"]'), github.actor) steps: - name: Checkout Repo @@ -27,13 +28,13 @@ jobs: - name: Authenticate gh run: echo "${{ secrets.GITHUB_TOKEN }}" | gh auth login --with-token - - name: Approve dependabot PRs + - name: Approve bot PR run: gh pr review --approve "$PR_URL" env: PR_URL: ${{ github.event.pull_request.html_url }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Merge for dependabot PRs + - name: Enable auto-merge for bot PR run: gh pr merge --auto --squash "$PR_URL" env: PR_URL: ${{ github.event.pull_request.html_url }} From b0b1eebb1a009bc41a08679b6a2678dc60cf4aa4 Mon Sep 17 00:00:00 2001 From: Javier Lougedo Date: Wed, 9 Sep 2026 15:35:13 +0200 Subject: [PATCH 2/4] ci(NOJIRA-1234): Drop smartling from the automerge allowlist This repo has no Smartling-managed content: the smartling-github-connector[bot] has never opened a PR here and there is no Smartling config. Narrow the allowlist to the bots that actually raise PRs in this repo. Co-Authored-By: Claude Opus 5 --- .github/workflows/dependabot-automerge.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dependabot-automerge.yml b/.github/workflows/dependabot-automerge.yml index f433140c..d374cfa0 100644 --- a/.github/workflows/dependabot-automerge.yml +++ b/.github/workflows/dependabot-automerge.yml @@ -11,8 +11,8 @@ permissions: jobs: automerge: runs-on: ubuntu-latest - # Trusted automation only. Keep this list in sync across blocks-owned repos. - if: contains(fromJSON('["dependabot[bot]", "smartling-github-connector[bot]", "aikido-autofix[bot]"]'), github.actor) + # Trusted automation only. Smartling is omitted: no translated content in this repo. + if: contains(fromJSON('["dependabot[bot]", "aikido-autofix[bot]"]'), github.actor) steps: - name: Checkout Repo From 26ede12ec5d4666a388a571339339c43be26f3b2 Mon Sep 17 00:00:00 2001 From: Javier Lougedo Date: Wed, 9 Sep 2026 17:34:13 +0200 Subject: [PATCH 3/4] ci(NOJIRA-1234): Hold risky bot dependency PRs for human review A green CI run does not prove a transitive dependency bump is safe: the repo's own tests never exercise how the intermediate package uses the changed API. Approve as before, but only arm auto-merge when the diff looks routine. Held back for a human when any of these match: - the bot's title declares a major version upgrade - a JS manifest touches resolutions/overrides (a forced transitive pin) - go.mod gains a +incompatible major bump - more than 6 manifest dependency lines change at once - the lockfile rewrite exceeds 600 lines Validated against 13 real bot PRs: correctly holds xfiles#543 (docker v24->v25 +incompatible), blocks#3039 (major axios), renderer#1481 and mail-composer#400 (forced resolutions), and correctly passes the single direct minor bumps such as embed#760, pages#620 and purgatory#314. Also drops the checkout and 'apt-get install gh' steps: nothing read the working tree (gh is API-only) and gh ships on ubuntu-latest. All repos now hold a byte-identical file apart from the allowlist line. Co-Authored-By: Claude Opus 5 --- .github/workflows/dependabot-automerge.yml | 81 ++++++++++++++++++---- 1 file changed, 68 insertions(+), 13 deletions(-) diff --git a/.github/workflows/dependabot-automerge.yml b/.github/workflows/dependabot-automerge.yml index d374cfa0..c0ea4635 100644 --- a/.github/workflows/dependabot-automerge.yml +++ b/.github/workflows/dependabot-automerge.yml @@ -11,31 +11,86 @@ permissions: jobs: automerge: runs-on: ubuntu-latest - # Trusted automation only. Smartling is omitted: no translated content in this repo. + # Trusted automation only. Keep this list in sync across blocks-owned repos. if: contains(fromJSON('["dependabot[bot]", "aikido-autofix[bot]"]'), github.actor) steps: - - name: Checkout Repo - uses: actions/checkout@v6 - with: - fetch-depth: 1 + - name: Approve bot PR + run: gh pr review --approve "$PR_URL" + env: + PR_URL: ${{ github.event.pull_request.html_url }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Install GitHub CLI + # A green CI run does not prove a transitive bump is safe: the repo's own + # tests never exercise how the intermediate package uses the changed API. + # Anything matching below is left for a human instead of auto-merging. + - name: Assess dependency risk + id: risk run: | - sudo apt-get update - sudo apt-get install -y gh + FILES=$(gh api "repos/$REPO/pulls/$PR_NUMBER/files" --paginate) + manifests() { + printf '%s' "$FILES" | jq -r ".[] | select(.filename|test(\"$1\")) | .patch // \"\"" + } + REASON="" - - name: Authenticate gh - run: echo "${{ secrets.GITHUB_TOKEN }}" | gh auth login --with-token + # 1. The bot names major bumps in its own title. + if printf '%s' "$PR_TITLE" | grep -qiE 'major version upgrade'; then + REASON="major version upgrade" + fi - - name: Approve bot PR - run: gh pr review --approve "$PR_URL" + # 2. A forced transitive pin in a JS manifest (resolutions/overrides/glob). + if [ -z "$REASON" ] && manifests 'package\\.json$' \ + | grep -qE '^[ +-].*"(resolutions|overrides)"[[:space:]]*:|^\+[[:space:]]*"\*\*/'; then + REASON="forced transitive override" + fi + + # 3. A Go major bump. + if [ -z "$REASON" ] && manifests 'go\\.mod$' | grep -qE '^\+.*\+incompatible'; then + REASON="go major (+incompatible) bump" + fi + + # 4. A bulk sweep across many packages at once. + N=$(manifests '(go\\.mod|package\\.json)$' \ + | grep -cE '^\+[[:space:]]+([a-zA-Z0-9./_-]+ v[0-9]|"[^"]+"[[:space:]]*:)' || true) + if [ -z "$REASON" ] && [ "${N:-0}" -gt 6 ]; then + REASON="bulk sweep ($N dependency lines changed)" + fi + + # 5. A wide lockfile rewrite means many transitive packages moved, + # even when the manifest diff looks small. + CHURN=$(printf '%s' "$FILES" \ + | jq '[.[] | select(.filename|test("(yarn\\.lock|package-lock\\.json|go\\.sum|pnpm-lock\\.yaml)$")) | .additions + .deletions] | add // 0') + if [ -z "$REASON" ] && [ "${CHURN:-0}" -gt 600 ]; then + REASON="wide lockfile rewrite ($CHURN lines)" + fi + + if [ -n "$REASON" ]; then + echo "risky=true" >> "$GITHUB_OUTPUT" + echo "reason=$REASON" >> "$GITHUB_OUTPUT" + echo "::warning::Not auto-merging: $REASON" + else + echo "risky=false" >> "$GITHUB_OUTPUT" + fi env: - PR_URL: ${{ github.event.pull_request.html_url }} + REPO: ${{ github.repository }} + PR_NUMBER: ${{ github.event.pull_request.number }} + PR_TITLE: ${{ github.event.pull_request.title }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Enable auto-merge for bot PR + if: steps.risk.outputs.risky == 'false' run: gh pr merge --auto --squash "$PR_URL" env: PR_URL: ${{ github.event.pull_request.html_url }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Flag risky bot PR for human review + if: steps.risk.outputs.risky == 'true' + run: | + gh pr edit "$PR_URL" --add-label needs-human + gh pr comment "$PR_URL" --body \ + "Not auto-merged: **$REASON**. A green CI run does not prove a transitive dependency bump is safe, so this one needs a human look." + env: + PR_URL: ${{ github.event.pull_request.html_url }} + REASON: ${{ steps.risk.outputs.reason }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From b570d4ed35f3270f0f98fe5a9abae1cd29bdabe3 Mon Sep 17 00:00:00 2001 From: Javier Lougedo Date: Wed, 9 Sep 2026 18:01:18 +0200 Subject: [PATCH 4/4] ci(NOJIRA-1234): Withhold approval from risky bot PRs Assess risk first, then approve only when the diff looks routine. A risky PR now gets no approval at all, so it cannot satisfy the required-review count and a human has to sign it off - the label alone was advisory, since a bot approval already met the review requirement. Also close the stale-arming gap: a PR can open looking routine (approved, auto-merge armed) and then be force-pushed into something risky. On the risky path the workflow now calls 'gh pr merge --disable-auto' and dismisses its own earlier approval before labelling and commenting. Co-Authored-By: Claude Opus 5 --- .github/workflows/dependabot-automerge.yml | 33 ++++++++++++++++------ 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/.github/workflows/dependabot-automerge.yml b/.github/workflows/dependabot-automerge.yml index c0ea4635..11e46e62 100644 --- a/.github/workflows/dependabot-automerge.yml +++ b/.github/workflows/dependabot-automerge.yml @@ -15,15 +15,10 @@ jobs: if: contains(fromJSON('["dependabot[bot]", "aikido-autofix[bot]"]'), github.actor) steps: - - name: Approve bot PR - run: gh pr review --approve "$PR_URL" - env: - PR_URL: ${{ github.event.pull_request.html_url }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # A green CI run does not prove a transitive bump is safe: the repo's own # tests never exercise how the intermediate package uses the changed API. - # Anything matching below is left for a human instead of auto-merging. + # Anything matching below is neither approved nor auto-merged, so it cannot + # satisfy the required-review count and a human has to sign it off. - name: Assess dependency risk id: risk run: | @@ -77,6 +72,13 @@ jobs: PR_TITLE: ${{ github.event.pull_request.title }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Approve bot PR + if: steps.risk.outputs.risky == 'false' + run: gh pr review --approve "$PR_URL" + env: + PR_URL: ${{ github.event.pull_request.html_url }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Enable auto-merge for bot PR if: steps.risk.outputs.risky == 'false' run: gh pr merge --auto --squash "$PR_URL" @@ -84,13 +86,26 @@ jobs: PR_URL: ${{ github.event.pull_request.html_url }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Flag risky bot PR for human review + - name: Hold risky bot PR for human review if: steps.risk.outputs.risky == 'true' run: | + # An earlier run may have approved and armed this PR while it still + # looked routine, so undo both before flagging it. + gh pr merge --disable-auto "$PR_URL" || true + + gh api "repos/$REPO/pulls/$PR_NUMBER/reviews" \ + --jq '.[] | select(.state == "APPROVED" and .user.login == "github-actions[bot]") | .id' \ + | while read -r id; do + gh api -X PUT "repos/$REPO/pulls/$PR_NUMBER/reviews/$id/dismissals" \ + -f message="Withdrawn: $REASON" >/dev/null || true + done + gh pr edit "$PR_URL" --add-label needs-human gh pr comment "$PR_URL" --body \ - "Not auto-merged: **$REASON**. A green CI run does not prove a transitive dependency bump is safe, so this one needs a human look." + "Held for human review: **$REASON**. A green CI run does not prove a transitive dependency bump is safe, so this PR was deliberately **not** approved and auto-merge is off. It needs a human to review and merge it." env: PR_URL: ${{ github.event.pull_request.html_url }} + REPO: ${{ github.repository }} + PR_NUMBER: ${{ github.event.pull_request.number }} REASON: ${{ steps.risk.outputs.reason }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}