Skip to content

Commit f1de4a7

Browse files
authored
fix: resync ci_dependabot_automerge.yml with guidance@main (#20) (#22)
1 parent ac739e9 commit f1de4a7

1 file changed

Lines changed: 85 additions & 12 deletions

File tree

.github/workflows/ci_dependabot_automerge.yml

Lines changed: 85 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,31 @@ on:
1212
pull_request_target:
1313
types: [opened, synchronize, reopened]
1414

15+
# Read-only. Every write this workflow performs - approving and merging -
16+
# uses DEPENDABOT_AUTOMERGE_TOKEN, not GITHUB_TOKEN, so GITHUB_TOKEN needs no
17+
# write scope. That matters more than usual here: the job runs under
18+
# pull_request_target, so a compromised step or action would otherwise hold a
19+
# repo-write token.
1520
permissions:
16-
contents: write
17-
pull-requests: write
21+
contents: read
22+
pull-requests: read
1823
checks: read
1924

2025
jobs:
2126
automerge:
2227
runs-on: ubuntu-latest
23-
if: github.actor == 'dependabot[bot]'
28+
# The PR's author, not github.actor. github.actor is whoever triggered
29+
# the event, so on `reopened` - or when anyone else pushes to the branch -
30+
# it is that person, this condition goes false, and the job silently does
31+
# nothing on a real Dependabot PR. Nothing reports that; it just never
32+
# merges.
33+
#
34+
# This deliberately does NOT decide whether a human has touched the
35+
# branch. That is a separate question with a separate answer - see the
36+
# commit-authorship check in the merge step - because the two were
37+
# previously conflated here, and the accidental protection it gave was
38+
# paid for by silent no-ops.
39+
if: github.event.pull_request.user.login == 'dependabot[bot]'
2440
env:
2541
# Auto-merge is opt-in per repository, and provisioning
2642
# DEPENDABOT_AUTOMERGE_TOKEN is what opts in. Without it every step
@@ -40,11 +56,16 @@ jobs:
4056
steps:
4157
- name: Report that auto-merge is not enabled
4258
if: env.HAS_AUTOMERGE_PAT != 'true'
43-
run: |
44-
echo "::notice::DEPENDABOT_AUTOMERGE_TOKEN is not set for this \
45-
repository, so Dependabot auto-merge is disabled and this PR will \
46-
not be merged automatically. See runbooks/dependabot-automerge.md \
47-
in vln-devsecops/guidance to provision it."
59+
env:
60+
# Folded scalar rather than shell line continuations: a backslash
61+
# continuation inside the quoted string embeds the indentation of
62+
# each continued line into the notice text.
63+
MSG: >-
64+
DEPENDABOT_AUTOMERGE_TOKEN is not set for this repository, so
65+
Dependabot auto-merge is disabled and this PR will not be merged
66+
automatically. See runbooks/dependabot-automerge.md in
67+
vln-devsecops/guidance to provision it.
68+
run: echo "::notice::$MSG"
4869

4970
- name: Fetch Dependabot metadata
5071
id: meta
@@ -79,8 +100,7 @@ jobs:
79100
# yet. A short fixed sleep is not enough: a queued Windows or
80101
# self-hosted runner can take minutes to post its first check run,
81102
# and "no checks yet" is indistinguishable from "this repo has no
82-
# other CI". Concluding success too early fails OPEN and merges
83-
# unguarded, so only give up waiting after this window.
103+
# other CI". This window separates the two.
84104
settle_seconds=180
85105
# Overall bound once checks have appeared.
86106
max_seconds=1800
@@ -114,8 +134,21 @@ jobs:
114134
sleep "$interval"
115135
continue
116136
fi
117-
echo "No sibling check runs appeared within ${settle_seconds}s; nothing to gate on."
118-
exit 0
137+
# Fail closed. A repo with no check at all on a Dependabot PR
138+
# has nothing for this gate to gate on, and merging anyway
139+
# would be the original unguarded behaviour wearing a gate's
140+
# name - the PR would look checked because this job went green.
141+
#
142+
# Auto-merge is only supposed to be enabled once a repo has at
143+
# least one PR-triggered check (see
144+
# runbooks/dependabot-automerge.md), so reaching this point
145+
# means the repo is misconfigured. Say so, loudly, rather than
146+
# merging and looking correct.
147+
echo "No sibling check runs appeared on $SHA within" \
148+
"${settle_seconds}s. Refusing to auto-merge: there is" \
149+
"nothing gating this PR. Add a PR-triggered check, or" \
150+
"remove this workflow."
151+
exit 1
119152
fi
120153
121154
incomplete=$(jq '[.[] | select(.status != "completed")] | length' <<<"$runs_json")
@@ -163,8 +196,48 @@ jobs:
163196
(steps.meta.outputs.update-type == 'version-update:semver-minor' ||
164197
steps.meta.outputs.update-type == 'version-update:semver-patch')
165198
run: |
199+
set -euo pipefail
200+
201+
# The gate above validated $SHA, the head at the time this run
202+
# started. If Dependabot has pushed since (a rebase, a follow-up
203+
# bump), the PR now points at a commit this run never checked -
204+
# and `gh pr merge --auto` applies to the PR, not to a SHA, so it
205+
# would enable auto-merge for that unchecked head. Without a
206+
# required status check there is nothing downstream to catch it.
207+
#
208+
# Bail out and let the run triggered by that newer commit do the
209+
# gating. Exit 0, not 1: being superseded is normal, not a failure.
210+
current=$(gh pr view "$PR_URL" --json headRefOid --jq .headRefOid)
211+
if [ "$current" != "$SHA" ]; then
212+
echo "PR head moved $SHA -> $current since this run started;" \
213+
"leaving it to the run for $current."
214+
exit 0
215+
fi
216+
217+
# Auto-merge covers what Dependabot wrote, and nothing else. If
218+
# anyone has pushed onto the branch, the PR now carries changes no
219+
# one reviewed and that the "it's only a version bump" reasoning
220+
# does not apply to, so hand it back to a human.
221+
#
222+
# Commits with no resolvable author count as foreign: an unmatched
223+
# commit email is exactly what an unexpected push looks like, and
224+
# guessing in favour of merging is the wrong way to be wrong.
225+
foreign=$(gh pr view "$PR_URL" --json commits --jq '
226+
[ .commits[]
227+
| (.authors // [])
228+
| if length == 0 then ["unknown"] else map(.login // "unknown") end
229+
| .[]
230+
] | unique | map(select(. != "dependabot[bot]")) | join(", ")')
231+
if [ -n "$foreign" ]; then
232+
echo "::notice::PR carries commits authored by: $foreign." \
233+
"Auto-merge only covers Dependabot's own commits, so this" \
234+
"PR is left for human review."
235+
exit 0
236+
fi
237+
166238
gh pr review --approve "$PR_URL"
167239
gh pr merge --auto --squash "$PR_URL"
168240
env:
169241
PR_URL: ${{ github.event.pull_request.html_url }}
242+
SHA: ${{ github.event.pull_request.head.sha }}
170243
GH_TOKEN: ${{ secrets.DEPENDABOT_AUTOMERGE_TOKEN }}

0 commit comments

Comments
 (0)