Repository navigation
Auto-merge workflow #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,119 @@ | ||||||||||||||||
| name: Dependabot auto-merge | ||||||||||||||||
| "on": | ||||||||||||||||
| # Use pull_request_target instead of pull_request to get elevated permissions | ||||||||||||||||
| # This is safe for Dependabot PRs because: | ||||||||||||||||
| # 1. We verify the PR author is dependabot[bot] | ||||||||||||||||
| # 2. We don't check out or run code from the PR | ||||||||||||||||
| # 3. We only enable auto-merge, which requires branch protection to pass | ||||||||||||||||
| pull_request_target: | ||||||||||||||||
| types: [opened, reopened, synchronize] | ||||||||||||||||
| pull_request_review: | ||||||||||||||||
| types: [submitted] | ||||||||||||||||
| check_suite: | ||||||||||||||||
| types: [completed] | ||||||||||||||||
|
|
||||||||||||||||
| permissions: | ||||||||||||||||
| contents: write | ||||||||||||||||
| pull-requests: write | ||||||||||||||||
|
Comment on lines
+15
to
+17
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial | 💤 Low value Document why these permissions are needed. Zizmor flags these as undocumented. While both permissions are legitimately required here ( permissions:
# Required for gh pr merge and patching repository allow_auto_merge setting
contents: write
# Required for enabling auto-merge and posting `@dependabot` rebase comments
pull-requests: writeYou could also scope these to the job level for marginally tighter blast radius, though with a single job it's mostly ceremonial. 🧰 Tools🪛 zizmor (1.25.2)[error] 16-16: overly broad permissions (excessive-permissions): contents: write is overly broad at the workflow level (excessive-permissions) [error] 17-17: overly broad permissions (excessive-permissions): pull-requests: write is overly broad at the workflow level (excessive-permissions) [warning] 16-16: permissions without explanatory comments (undocumented-permissions): needs an explanatory comment (undocumented-permissions) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||||||||||||||||
|
|
||||||||||||||||
| jobs: | ||||||||||||||||
| dependabot: | ||||||||||||||||
| runs-on: ubuntu-latest | ||||||||||||||||
|
Comment on lines
+19
to
+21
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial | 💤 Low value Consider adding a descriptive job name. The job is currently anonymous. Adding a jobs:
dependabot:
+ name: Enable auto-merge for Dependabot PRs
runs-on: ubuntu-latestPurely cosmetic, but your future self debugging at 11pm will thank you. 📝 Committable suggestion
Suggested change
🧰 Tools🪛 zizmor (1.25.2)[info] 20-20: workflow or action definition without a name (anonymous-definition): this job (anonymous-definition) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||||||||||||||||
| if: >- | ||||||||||||||||
| (github.event_name == 'pull_request_target' && github.event.pull_request.user.login == 'dependabot[bot]' && | ||||||||||||||||
| github.event.pull_request.base.ref == 'main') || (github.event_name == 'pull_request_review' && | ||||||||||||||||
| github.event.pull_request.user.login == 'dependabot[bot]' && github.event.pull_request.base.ref == 'main') || | ||||||||||||||||
| (github.event_name == 'check_suite' && github.event.check_suite.pull_requests[0] != null && | ||||||||||||||||
| startsWith(github.event.check_suite.head_branch, 'dependabot/')) | ||||||||||||||||
| steps: | ||||||||||||||||
| - name: Get PR details | ||||||||||||||||
| id: pr | ||||||||||||||||
| # jq's // empty alternative operator returns an empty string instead of | ||||||||||||||||
| # the literal "null" when a key is absent or null in the event payload. | ||||||||||||||||
| # This ensures the downstream empty/null/numeric checks work correctly. | ||||||||||||||||
| run: | | ||||||||||||||||
| if [ "${GITHUB_EVENT_NAME}" = "check_suite" ]; then | ||||||||||||||||
| PR_NUMBER="$(jq -r '.check_suite.pull_requests[0].number // empty' "${GITHUB_EVENT_PATH}")" | ||||||||||||||||
| if [ -z "$PR_NUMBER" ] || [ "$PR_NUMBER" = "null" ] || ! [[ "$PR_NUMBER" =~ ^[0-9]+$ ]]; then | ||||||||||||||||
| echo "No valid PR number found in check_suite event (got: $PR_NUMBER)" | ||||||||||||||||
| exit 1 | ||||||||||||||||
| fi | ||||||||||||||||
| PR_JSON=$(gh pr view "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --json author,baseRefName) | ||||||||||||||||
| AUTHOR=$(echo "$PR_JSON" | jq -r '.author.login') | ||||||||||||||||
| BASE_REF=$(echo "$PR_JSON" | jq -r '.baseRefName') | ||||||||||||||||
|
|
||||||||||||||||
| { | ||||||||||||||||
| echo "author=$AUTHOR" | ||||||||||||||||
| echo "base_ref=$BASE_REF" | ||||||||||||||||
| echo "number=$PR_NUMBER" | ||||||||||||||||
| } >> "$GITHUB_OUTPUT" | ||||||||||||||||
| else | ||||||||||||||||
| # For pull_request_target and pull_request_review, use event data | ||||||||||||||||
| { | ||||||||||||||||
| echo "author=$(jq -r '.pull_request.user.login // empty' "${GITHUB_EVENT_PATH}")" | ||||||||||||||||
| echo "base_ref=$(jq -r '.pull_request.base.ref // empty' "${GITHUB_EVENT_PATH}")" | ||||||||||||||||
| echo "number=$(jq -r '.pull_request.number // empty' "${GITHUB_EVENT_PATH}")" | ||||||||||||||||
| } >> "$GITHUB_OUTPUT" | ||||||||||||||||
| fi | ||||||||||||||||
| env: | ||||||||||||||||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||||||||||||||||
|
|
||||||||||||||||
| - name: Enable auto-merge for Dependabot PRs | ||||||||||||||||
| if: steps.pr.outputs.author == 'dependabot[bot]' && steps.pr.outputs.base_ref == 'main' | ||||||||||||||||
| shell: bash | ||||||||||||||||
| env: | ||||||||||||||||
| PR_NUMBER: ${{ steps.pr.outputs.number }} | ||||||||||||||||
| GH_TOKEN: ${{ secrets.WORKFLOW_PAT }} | ||||||||||||||||
|
Comment on lines
+64
to
+66
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial | 💤 Low value Quick note: You're using a separate PAT rather than
If this secret isn't configured or has insufficient scopes, the workflow will fail somewhat opaquely. Might be worth a brief comment or a check that the secret exists (though GitHub will fail fast if it doesn't). 🤖 Prompt for AI Agents |
||||||||||||||||
| # yamllint disable rule:line-length | ||||||||||||||||
| run: | | ||||||||||||||||
| set -o pipefail | ||||||||||||||||
|
|
||||||||||||||||
| attempt_merge() { | ||||||||||||||||
| gh pr merge --auto --rebase "${PR_NUMBER}" --repo "$GITHUB_REPOSITORY" 2>&1 | tee /tmp/gh-output.txt | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| if attempt_merge; then | ||||||||||||||||
| exit 0 | ||||||||||||||||
| fi | ||||||||||||||||
|
|
||||||||||||||||
| # Not a real failure: requirements not yet met, or auto-merge already enabled. | ||||||||||||||||
| if grep -qE "auto-merge is already enabled|[Rr]equired.*status.*check|[Rr]equired approving review|[Rr]equired.*review" /tmp/gh-output.txt; then | ||||||||||||||||
| echo "Auto-merge not enabled yet - this is expected when requirements are not met or already enabled" | ||||||||||||||||
| exit 0 | ||||||||||||||||
| fi | ||||||||||||||||
|
|
||||||||||||||||
| # The repository-level "Allow auto-merge" setting is off. Re-enable it and retry once. | ||||||||||||||||
| # This can happen if the setting is accidentally toggled in the repository admin UI. | ||||||||||||||||
| if grep -qF "Auto merge is not allowed for this repository" /tmp/gh-output.txt; then | ||||||||||||||||
| echo "Repository Allow auto-merge is disabled; attempting to re-enable..." | ||||||||||||||||
| if ! gh api "repos/${GITHUB_REPOSITORY}" --method PATCH --field allow_auto_merge=true > /dev/null; then | ||||||||||||||||
| echo "Could not re-enable Allow auto-merge on the repository. Manual intervention required." | ||||||||||||||||
| exit 1 | ||||||||||||||||
| fi | ||||||||||||||||
| echo "Repository setting re-enabled; retrying..." | ||||||||||||||||
| if ! attempt_merge; then | ||||||||||||||||
| echo "Auto-merge still failed after re-enabling repository setting:" | ||||||||||||||||
| cat /tmp/gh-output.txt | ||||||||||||||||
| exit 1 | ||||||||||||||||
| fi | ||||||||||||||||
| exit 0 | ||||||||||||||||
| fi | ||||||||||||||||
|
|
||||||||||||||||
| # Concurrent merge race: another PR merged while this one was being processed, | ||||||||||||||||
| # moving the base branch. Request a Dependabot rebase to update this branch; | ||||||||||||||||
| # this workflow re-triggers naturally via check_suite:completed when the rebased | ||||||||||||||||
| # branch passes checks. With N concurrent Dependabot PRs, this converges in N-1 | ||||||||||||||||
| # rounds: each round merges one PR, and any PRs that fall behind again each get | ||||||||||||||||
| # a fresh rebase request on the next trigger. | ||||||||||||||||
| if grep -qF "Base branch was modified" /tmp/gh-output.txt; then | ||||||||||||||||
| echo "Base branch was modified by a concurrent merge. Requesting Dependabot rebase..." | ||||||||||||||||
| gh pr comment "${PR_NUMBER}" --repo "$GITHUB_REPOSITORY" --body "@dependabot rebase" || true | ||||||||||||||||
| echo "Rebase requested; workflow will re-trigger when the rebased branch passes checks." | ||||||||||||||||
| exit 0 | ||||||||||||||||
| fi | ||||||||||||||||
|
Comment on lines
+108
to
+113
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Silent comment failure could leave PR stuck without visibility. The Consider at least logging when this fails so it's visible in the workflow logs: - gh pr comment "${PR_NUMBER}" --repo "$GITHUB_REPOSITORY" --body "`@dependabot` rebase" || true
+ if ! gh pr comment "${PR_NUMBER}" --repo "$GITHUB_REPOSITORY" --body "`@dependabot` rebase"; then
+ echo "::warning::Failed to post `@dependabot` rebase comment. PR may need manual attention."
+ fiThis keeps the workflow from failing hard (since the comment isn't strictly required for forward progress—Dependabot might pick it up via other triggers), but gives you breadcrumbs when debugging why a PR went stale. 🤖 Prompt for AI Agents |
||||||||||||||||
|
|
||||||||||||||||
| # Unexpected error. | ||||||||||||||||
| echo "Unexpected error enabling auto-merge:" | ||||||||||||||||
| cat /tmp/gh-output.txt | ||||||||||||||||
| exit 1 | ||||||||||||||||
| # yamllint enable | ||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a concurrency group to prevent race conditions.
Without a concurrency limit, multiple workflow runs triggered by concurrent Dependabot PRs or rapid event sequences can step on each other—especially when toggling
allow_auto_mergeor racing through the "Base branch was modified" path. This could leave you in a weird state where one run's retry collides with another run's attempt.A simple group keyed on PR number (with
check_suitefalling back to branch) should serialize runs for the same PR:Using
cancel-in-progress: falselets each run complete rather than canceling mid-flight (which could leave things half-done).🧰 Tools
🪛 zizmor (1.25.2)
[error] 2-13: use of fundamentally insecure workflow trigger (dangerous-triggers): pull_request_target is almost always used insecurely
(dangerous-triggers)
[warning] 2-13: insufficient job-level concurrency limits (concurrency-limits): workflow is missing concurrency setting
(concurrency-limits)
🤖 Prompt for AI Agents
Source: Linters/SAST tools