diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml index 1a789af..3c7c9a5 100644 --- a/.github/workflows/dependabot-auto-merge.yml +++ b/.github/workflows/dependabot-auto-merge.yml @@ -121,8 +121,34 @@ jobs: steps.meta.outputs.update-type != '' && contains(format(',{0},', inputs.allowed-update-types), format(',{0},', steps.meta.outputs.update-type)) - run: gh pr merge --auto --${{ inputs.merge-method }} "$PR_URL" + # Two failure modes this handles, both seen in production on the + # wemove.eu / youmove / wemove-charity.eu inline copies this replaces: + # + # 1. `gh pr merge --auto` intermittently returns a transient GraphQL + # error, which strands the PR until a human notices. + # 2. Auto-merge cannot be enabled on a PR whose required checks have + # ALREADY passed. With fast CI the checks often win the race, and + # the PR then sits green and unmerged forever. + # + # So: retry, and fall back to a direct merge. The direct merge is not a + # bypass — branch protection still refuses it while required checks are + # pending or failing. On a repo with NO required checks it would merge + # immediately, which is exactly why a ruleset is part of the entry bar + # for using this workflow at all (see the note at the top). + # + # merge-method goes through env, not `${{ }}` interpolation into the + # script body, so a caller cannot inject shell through it. + run: | + for attempt in 1 2 3 4 5; do + if gh pr merge --auto --"$MERGE_METHOD" "$PR_URL"; then exit 0; fi + if gh pr merge --"$MERGE_METHOD" "$PR_URL"; then exit 0; fi + echo "attempt $attempt: could not enable auto-merge yet; retrying in 15s..." + sleep 15 + done + echo "::error::failed to enable auto-merge after 5 attempts" + exit 1 env: PR_URL: ${{ github.event.pull_request.html_url }} + MERGE_METHOD: ${{ inputs.merge-method }} # Falls back to GITHUB_TOKEN when the App token step was skipped. GH_TOKEN: ${{ steps.app-token.outputs.token || secrets.GITHUB_TOKEN }} diff --git a/README.md b/README.md index 5637d3d..d23a4bf 100644 --- a/README.md +++ b/README.md @@ -167,6 +167,13 @@ depends on a branch ruleset (require a PR + required checks) on the consuming re Without that ruleset `gh pr merge --auto` has nothing to wait for and the PR merges immediately. +Arming is retried five times, and falls back to a direct merge. That covers two real +failure modes: `gh pr merge --auto` intermittently returns a transient GraphQL error, and +auto-merge cannot be enabled at all on a PR whose required checks have *already* gone +green — which fast CI makes common, and which otherwise leaves the PR sitting green and +unmerged. The direct merge is not a bypass: branch protection still refuses it while +required checks are pending or failing. + ### Inputs - **merge-method**: `merge` | `squash` | `rebase`. Default `squash`. @@ -261,6 +268,7 @@ jobs: Version tags `v1`–`v13` predate the current scheme and are frozen point releases. The floating-major convention (see [`RELEASING.md`](RELEASING.md)) starts at **`v14`**. +- **v14.2** — `dependabot-auto-merge.yml`: arming is now retried (5 attempts) with a direct-merge fallback, so a transient GraphQL error no longer strands a PR and an already-green PR still merges. Consolidates logic that existed only in the inline copies in wemove.eu, youmove, wemove-charity.eu and pubstatic, which this lets them drop. - **v14.1** — `dependabot-auto-merge.yml`: accepts optional `app-id` / `app-private-key` secrets so the merge is armed with a GitHub App token and fires a real push event (a `GITHUB_TOKEN`-armed merge does not, leaving push-triggered deploys silently unrun). Falls back to `GITHUB_TOKEN` when the secrets are omitted, and warns. The update-type gate is now a whole-token, fail-closed match — previously an empty `update-type` from `fetch-metadata` satisfied `contains()` and could auto-merge a major. - **v14** — First release under the semver + floating-major scheme. Ships the `docker-smoke` composite action (build + run + production-Host-header probe, with a `build-only` mode). - **v13** — Adds a reusable Dependabot auto-merge workflow and an actionlint CI gate.