From 75050fc3f298eb85ecc3ba8a3e60b6f037b0cf3b Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sun, 30 Aug 2026 09:07:33 +0200 Subject: [PATCH] fix(fleet-merge): enforce the author trust it documents, and be able to merge Two bugs that cancelled out, so the routine ran green weekly and merged nothing. 1. --admin was never passed. Development Branch Protection requires 1 approving review and grants bypass to OrganizationAdmin only, so a plain squash could not merge. The 2026-08-28 run: 3 merged/would-merge, 46 skipped, every real attempt 'merge refused by GitHub'. 2. The author was read and never tested, despite the header promising to refuse authors not trusted for unattended merge. Fixing only (1) would have started merging functional work without review. Also keeps the refusal text. The old branch discarded it, which is why 'refused by GitHub' named no reason and the cause went undiagnosed. --- .github/workflows/fleet-friday-merge.yml | 47 +++++++++++++++++++++++- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/.github/workflows/fleet-friday-merge.yml b/.github/workflows/fleet-friday-merge.yml index 02eb36e2..112707f4 100644 --- a/.github/workflows/fleet-friday-merge.yml +++ b/.github/workflows/fleet-friday-merge.yml @@ -145,6 +145,34 @@ jobs: SKIPPED=$((SKIPPED + 1)); continue fi + # TRUSTED AUTHORS ONLY. + # + # The header above has always said this workflow refuses "PRs + # whose author is not trusted for unattended merge". Nothing + # enforced it: the author was read on the `while` line above and + # then only ever printed. Every green human PR was a candidate + # for an unattended merge, and the sole reason none happened is + # that GitHub refused them all for an unrelated reason (see + # --admin below). Two bugs cancelling out is not a safety + # property, and fixing only the second would have turned this + # into a workflow that merges functional work without review. + # + # Dependency bumps are the class that is safe unattended: the + # diff is a version number, and the gates that judge it are the + # same ones a human reviewer would read. Functional work keeps + # its review. + # + # `dependabot[bot]` is quoted deliberately — unquoted, the + # brackets are a case-pattern character class matching + # `dependabott`, not the literal login. + case "${author}" in + app/dependabot|dependabot|'dependabot[bot]') ;; + *) + srow "| \`${app}\` | #${pr} | skipped — author ${author} not trusted for unattended merge |" + SKIPPED=$((SKIPPED + 1)); continue + ;; + esac + states=$(gh pr checks "${pr}" --repo "${R}" --json state --jq '.[].state' 2>/dev/null) if [ -z "${states}" ]; then srow "| \`${app}\` | #${pr} | skipped — checks unreadable, NOT assumed passing |" @@ -172,11 +200,26 @@ jobs: MERGED=$((MERGED + 1)); continue fi - if gh pr merge "${pr}" --repo "${R}" --squash 2>/dev/null; then + # --admin, and why it is required rather than merely convenient. + # + # `Development Branch Protection` sets + # required_approving_review_count: 1 and grants bypass to + # OrganizationAdmin only. A plain squash from this token + # therefore CANNOT merge — and did not. The 2026-08-28 run + # reported "3 merged/would-merge, 46 skipped" with every real + # attempt logged as "merge refused by GitHub". The routine ran on + # schedule, reported success, and merged nothing, for weeks. + # + # The old branch discarded the error, which is exactly why the + # cause went undiagnosed for so long: "refused by GitHub" names + # no reason, so it read as an ordinary conflict. Keep the text — + # a refusal we cannot read is a refusal we will misattribute. + if merge_err=$(gh pr merge "${pr}" --repo "${R}" --squash --admin 2>&1); then srow "| \`${app}\` | #${pr} | **merged** (${total} checks green) |" MERGED=$((MERGED + 1)) else - srow "| \`${app}\` | #${pr} | merge refused by GitHub — left open |" + why=$(printf '%s' "${merge_err}" | tr '\n|' ' ' | cut -c1-120) + srow "| \`${app}\` | #${pr} | merge refused by GitHub — ${why} |" SKIPPED=$((SKIPPED + 1)) fi done <<< "${prs}"