Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 119 additions & 0 deletions .github/workflows/dependabot-auto-merge.yaml
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]
Comment on lines +1 to +13

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

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_merge or 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_suite falling back to branch) should serialize runs for the same PR:

concurrency:
  group: dependabot-auto-merge-${{ github.event.pull_request.number || github.event.check_suite.head_branch }}
  cancel-in-progress: false

Using cancel-in-progress: false lets 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
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/dependabot-auto-merge.yaml around lines 1 - 13, Add a
concurrency group to the dependabot-auto-merge workflow to prevent race
conditions when multiple runs execute concurrently. Add a concurrency section at
the workflow level (after the "on" trigger definition) with a group key that
uses the pull request number for pull_request events and falls back to the
check_suite head_branch for check_suite events, and set cancel-in-progress to
false to ensure each run completes fully rather than being canceled mid-flight,
preventing partial state changes.

Source: Linters/SAST tools


permissions:
contents: write
pull-requests: write
Comment on lines +15 to +17

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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 (contents: write for the repo settings PATCH and merge operation, pull-requests: write for merge and commenting), adding brief comments helps future you (or anyone else) understand the rationale without spelunking through the steps:

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: write

You 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 Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/dependabot-auto-merge.yaml around lines 15 - 17, Add
inline comments above each permission in the permissions block to document their
purpose. For the contents: write permission, explain that it is required for the
gh pr merge command and patching the repository allow_auto_merge setting. For
the pull-requests: write permission, explain that it is required for enabling
auto-merge and posting `@dependabot` rebase comments. These comments should
precede each permission on the line above to clarify the rationale without
requiring readers to trace through the workflow steps.

Source: Linters/SAST tools


jobs:
dependabot:
runs-on: ubuntu-latest
Comment on lines +19 to +21

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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 name field improves readability in the GitHub Actions UI and makes it clearer what's happening when you're debugging a stuck merge:

 jobs:
   dependabot:
+    name: Enable auto-merge for Dependabot PRs
     runs-on: ubuntu-latest

Purely cosmetic, but your future self debugging at 11pm will thank you.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
jobs:
dependabot:
runs-on: ubuntu-latest
jobs:
dependabot:
name: Enable auto-merge for Dependabot PRs
runs-on: ubuntu-latest
🧰 Tools
🪛 zizmor (1.25.2)

[info] 20-20: workflow or action definition without a name (anonymous-definition): this job

(anonymous-definition)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/dependabot-auto-merge.yaml around lines 19 - 21, The
dependabot job in the workflow is missing a descriptive name field, making it
harder to identify in the GitHub Actions UI. Add a `name` field to the
dependabot job with a clear, descriptive value (such as "Auto-merge Dependabot
PRs") positioned after the job key and before the `runs-on` field to improve
visibility and clarity when monitoring or debugging workflow execution.

Source: 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | 💤 Low value

Quick note: WORKFLOW_PAT needs appropriate scopes.

You're using a separate PAT rather than GITHUB_TOKEN here—presumably because the default token can't enable auto-merge or patch repo settings in all scenarios. Just confirming that WORKFLOW_PAT needs:

  • repo scope (or at minimum public_repo for public repos)
  • For the allow_auto_merge PATCH, admin access to the repository

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
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/dependabot-auto-merge.yaml around lines 64 - 66, The
`WORKFLOW_PAT` secret used in the workflow environment needs to be verified to
have the appropriate scopes and permissions to enable auto-merge functionality.
Ensure that the `WORKFLOW_PAT` secret configured in the repository settings has
the `repo` scope (or at minimum `public_repo` for public repositories) and admin
access to the repository, which is required for the auto-merge PATCH operation
to succeed. Consider adding a comment above the env section in the
dependabot-auto-merge.yaml workflow to document why a custom PAT is needed
instead of the default GITHUB_TOKEN, to help future maintainers understand the
setup requirements.

# 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Silent comment failure could leave PR stuck without visibility.

The || true on line 110 means if posting the @dependabot rebase comment fails (rate limit, network blip, permissions issue), the workflow exits successfully but Dependabot never gets the message. The PR just... sits there waiting for a rebase that was never requested.

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."
+            fi

This 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
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/dependabot-auto-merge.yaml around lines 108 - 113, The `gh
pr comment` command on line 110 silently fails due to the `|| true` clause,
which means if posting the `@dependabot rebase` comment fails (rate limit,
network issue, permissions), the workflow exits successfully without logging the
failure, leaving the PR stuck without visibility. Add logging to capture and
display the error when the comment command fails, while keeping the `|| true` to
prevent the workflow from exiting with a failure status. This way, the workflow
continues (since the comment isn't strictly required for forward progress), but
failures are visible in the logs for debugging purposes.


# Unexpected error.
echo "Unexpected error enabling auto-merge:"
cat /tmp/gh-output.txt
exit 1
# yamllint enable
Loading