From a008f706fedfee4764a40ef8ef227603cab5525c Mon Sep 17 00:00:00 2001 From: JacobPEvans <20714140+JacobPEvans@users.noreply.github.com> Date: Sun, 12 Apr 2026 09:42:52 -0400 Subject: [PATCH 1/3] feat(automerge): add scheduled sweep for stale auto-merge queue (RC3) GitHub's auto-merge sometimes goes stale: PR is CLEAN with auto-merge enabled but the merge never executes. Toggling auto-merge off/on forces re-evaluation and the merge completes within seconds. This replaces the unmerged 500-line healer (feat/automerge-reliability) with a 70-line workflow that runs every 10 minutes across all repos. (claude) --- .github/workflows/automerge-sweep.yml | 72 +++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 .github/workflows/automerge-sweep.yml diff --git a/.github/workflows/automerge-sweep.yml b/.github/workflows/automerge-sweep.yml new file mode 100644 index 0000000..150e803 --- /dev/null +++ b/.github/workflows/automerge-sweep.yml @@ -0,0 +1,72 @@ +# Automerge Sweep +# +# GitHub's auto-merge queue sometimes goes stale (RC3): a PR has +# mergeStateStatus=CLEAN and autoMergeRequest set, but GitHub never +# executes the merge. Toggling auto-merge off/on forces re-evaluation. +# +# Runs every 10 minutes. Idempotent — safe to run on any schedule. +name: automerge-sweep + +on: + schedule: + - cron: "*/10 * * * *" + workflow_dispatch: + +permissions: {} + +jobs: + sweep: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + strategy: + fail-fast: false + matrix: + repo: + - nix-darwin + - nix-ai + - nix-home + - nix-devenv + - ai-workflows + - ai-assistant-instructions + - ansible-proxmox + - ansible-proxmox-apps + - ansible-splunk + - terraform-proxmox + - terraform-aws-bedrock + - terraform-aws-static-website + - terraform-runs-on + - orbstack-kubernetes + steps: + - name: Generate GitHub App Token + id: app-token + uses: actions/create-github-app-token@v3 + with: + app-id: ${{ secrets.GH_ACTION_JACOBPEVANS_APP_ID }} + private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} + owner: JacobPEvans + repositories: ${{ matrix.repo }} + permission-contents: write + permission-pull-requests: write + + - name: Poke stuck trusted PRs + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + REPO: ${{ matrix.repo }} + run: | + gh pr list -R "JacobPEvans/${REPO}" --state open \ + --json number,author,autoMergeRequest,mergeStateStatus,isDraft \ + --jq ' + .[] | select( + .isDraft == false and + (.author.login | test("^(renovate|dependabot|jacobpevans-github-actions)")) and + (.autoMergeRequest != null) and + (.mergeStateStatus == "CLEAN") + ) | .number + ' | while read -r pr; do + echo "Poking JacobPEvans/${REPO}#${pr}" + gh pr merge "${pr}" -R "JacobPEvans/${REPO}" --disable-auto 2>/dev/null || true + sleep 2 + gh pr merge "${pr}" -R "JacobPEvans/${REPO}" --auto --squash || true + done From 418e8fa8fcaee5c996602677184c24f885e28b97 Mon Sep 17 00:00:00 2001 From: JacobPEvans <20714140+JacobPEvans@users.noreply.github.com> Date: Sun, 12 Apr 2026 11:05:52 -0400 Subject: [PATCH 2/3] =?UTF-8?q?fix(automerge):=20address=20Copilot=20revie?= =?UTF-8?q?w=20=E2=80=94=20concurrency,=20permissions,=20limit,=20regex,?= =?UTF-8?q?=20merge=20method,=20error=20surfacing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add workflow-level concurrency group (cancel-in-progress) to prevent overlapping sweeps - Set job permissions: {} — GITHUB_TOKEN is unused; App token carries its own permissions - Add --limit 1000 to gh pr list to avoid missing stuck PRs in busy repos - Tighten author regex to exact [bot] suffix + $ anchor (prevents false prefix matches) - Read autoMergeRequest.mergeMethod and pass appropriate flag instead of hard-coding --squash - Replace || true with ::warning:: annotations so failures surface in Actions logs --- .github/workflows/automerge-sweep.yml | 31 +++++++++++++++++++-------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/.github/workflows/automerge-sweep.yml b/.github/workflows/automerge-sweep.yml index 150e803..6cc05ed 100644 --- a/.github/workflows/automerge-sweep.yml +++ b/.github/workflows/automerge-sweep.yml @@ -12,14 +12,16 @@ on: - cron: "*/10 * * * *" workflow_dispatch: +concurrency: + group: automerge-sweep + cancel-in-progress: true + permissions: {} jobs: sweep: runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write + permissions: {} strategy: fail-fast: false matrix: @@ -55,18 +57,29 @@ jobs: GH_TOKEN: ${{ steps.app-token.outputs.token }} REPO: ${{ matrix.repo }} run: | - gh pr list -R "JacobPEvans/${REPO}" --state open \ + gh pr list -R "JacobPEvans/${REPO}" --state open --limit 1000 \ --json number,author,autoMergeRequest,mergeStateStatus,isDraft \ --jq ' .[] | select( .isDraft == false and - (.author.login | test("^(renovate|dependabot|jacobpevans-github-actions)")) and + (.author.login | test("^(renovate\\[bot\\]|dependabot\\[bot\\]|jacobpevans-github-actions\\[bot\\])$")) and (.autoMergeRequest != null) and (.mergeStateStatus == "CLEAN") - ) | .number - ' | while read -r pr; do + ) | [.number, .autoMergeRequest.mergeMethod] | @tsv + ' | while IFS=$'\t' read -r pr merge_method; do echo "Poking JacobPEvans/${REPO}#${pr}" - gh pr merge "${pr}" -R "JacobPEvans/${REPO}" --disable-auto 2>/dev/null || true + case "${merge_method}" in + MERGE) merge_flag="--merge" ;; + REBASE) merge_flag="--rebase" ;; + SQUASH) merge_flag="--squash" ;; + *) + echo "::warning::Unknown merge method '${merge_method}' for JacobPEvans/${REPO}#${pr}; defaulting to --squash" + merge_flag="--squash" + ;; + esac + gh pr merge "${pr}" -R "JacobPEvans/${REPO}" --disable-auto 2>/dev/null \ + || echo "::warning::Failed to disable auto-merge for JacobPEvans/${REPO}#${pr}" sleep 2 - gh pr merge "${pr}" -R "JacobPEvans/${REPO}" --auto --squash || true + gh pr merge "${pr}" -R "JacobPEvans/${REPO}" --auto "${merge_flag}" \ + || echo "::warning::Failed to re-enable auto-merge for JacobPEvans/${REPO}#${pr}" done From 6f6f11024320d6b327d8920d77cb249b4e3ddff8 Mon Sep 17 00:00:00 2001 From: JacobPEvans <20714140+JacobPEvans@users.noreply.github.com> Date: Sun, 12 Apr 2026 11:22:11 -0400 Subject: [PATCH 3/3] =?UTF-8?q?fix(automerge):=20drop=20permission-content?= =?UTF-8?q?s=20from=20App=20token=20=E2=80=94=20only=20pull-requests=20nee?= =?UTF-8?q?ded?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gh pr merge and gh pr list require pull-requests:write only; contents:write was unused and violates least-privilege. --- .github/workflows/automerge-sweep.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/automerge-sweep.yml b/.github/workflows/automerge-sweep.yml index 6cc05ed..62ec539 100644 --- a/.github/workflows/automerge-sweep.yml +++ b/.github/workflows/automerge-sweep.yml @@ -49,7 +49,6 @@ jobs: private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} owner: JacobPEvans repositories: ${{ matrix.repo }} - permission-contents: write permission-pull-requests: write - name: Poke stuck trusted PRs