Dependabot Auto-Merge with Policy-as-Code Guardrails

You want Dependabot to keep dependencies current without a human clicking merge on every routine patch — but not so autonomously that a compromised or breaking release sails into production unattended. The safe middle ground is a small amount of configuration that encodes a strict rule: auto-merge only patch and minor updates that pass every check, and route everything else to a person. This walkthrough gives you the three files that implement it — a dependabot.yml, a GitHub Actions workflow using dependabot/fetch-metadata and gh pr merge --auto, and the branch-protection settings that make the whole thing enforceable. It is the implementation companion to Automated Dependency Updates as Policy-as-Code, within Supply Chain & Dependency Security. The merge decision leans on the pass/fail signal from dependency scanning CI gates, so have that gate in place first.

Prerequisites

  • A GitHub repository with Actions enabled and admin access to configure branch protection
  • An existing CI workflow that runs your test suite and a dependency vulnerability scan on pull requests
  • Committed lockfiles so update PRs produce deterministic diffs
  • The gh CLI is available on GitHub-hosted runners by default; no extra install needed
  • Allow auto-merge enabled in repository Settings → General → Pull Requests

Expected Outcomes

  • Dependabot opens grouped, scheduled update PRs against a defined ecosystem
  • Patch and minor updates that pass all required checks merge automatically
  • Major version bumps are blocked and labelled for human review
  • Branch protection guarantees no update merges without green required checks

Step 1: Author dependabot.yml

Start with the policy file that tells Dependabot what to watch and how to shape its pull requests. Grouping non-major updates into one PR keeps the auto-merge surface small and reviewable, and separating majors makes them easy to route to a human.

# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"
      day: "monday"
    open-pull-requests-limit: 10
    labels:
      - "dependencies"
    groups:
      # One PR for all low-risk updates — the auto-merge target
      non-major:
        update-types:
          - "minor"
          - "patch"
    commit-message:
      prefix: "deps"

The groups block is the key policy choice: patch and minor updates arrive as a single grouped PR, while any major update comes as its own separate PR that will fall outside the auto-merge condition in Step 2. Commit this file to your default branch; Dependabot picks it up on its next scheduled run.

Step 2: Write the Auto-Merge Workflow with fetch-metadata

This workflow reads the update metadata Dependabot attaches to each PR and enables GitHub’s native auto-merge only when the update is a patch or minor. It uses pull_request_target so the GITHUB_TOKEN has the write access needed to approve and merge — Dependabot PRs get a read-only token under the plain pull_request event. Because pull_request_target is privileged, the workflow deliberately does not check out or run the PR’s code; it only calls fetch-metadata and gh.

# .github/workflows/dependabot-auto-merge.yml
name: dependabot-auto-merge
on: pull_request_target

permissions:
  contents: write
  pull-requests: write

jobs:
  auto-merge:
    runs-on: ubuntu-latest
    if: github.actor == 'dependabot[bot]'
    steps:
      - name: Fetch Dependabot metadata
        id: meta
        uses: dependabot/fetch-metadata@v2
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}

      - name: Approve patch and minor updates
        if: steps.meta.outputs.update-type == 'version-update:semver-patch' || steps.meta.outputs.update-type == 'version-update:semver-minor'
        run: gh pr review --approve "$PR_URL"
        env:
          PR_URL: ${{ github.event.pull_request.html_url }}
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Enable auto-merge for patch and minor updates
        if: steps.meta.outputs.update-type == 'version-update:semver-patch' || steps.meta.outputs.update-type == 'version-update:semver-minor'
        run: gh pr merge --auto --squash "$PR_URL"
        env:
          PR_URL: ${{ github.event.pull_request.html_url }}
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

The update-type output takes exactly one of version-update:semver-patch, version-update:semver-minor, or version-update:semver-major. The two conditional steps run only for the first two, so a major bump skips both the approval and the gh pr merge call — it simply sits open, approved by nobody, waiting for a reviewer. The --auto flag does not merge immediately; it queues the PR to merge once branch protection’s required checks pass, which is what Step 3 configures.

Step 3: Enforce Branch Protection and Required Checks

The workflow above is only a request to merge. The actual enforcement — that nothing merges without green checks and that majors need a human — lives in branch protection. Without this step, --auto would merge a patch as soon as it is approved even if tests were failing. The decision flow below shows how the three files interlock.

Dependabot Auto-Merge Decision Flow A Dependabot pull request enters a decision node testing the update type; patch and minor updates are approved and queued for auto-merge, which waits on required status checks before merging, while major updates are diverted to human review. Dependabot PR opened fetch-metadata update-type? patch / minor major Approve & queue gh pr merge --auto Human Review label, await approval Required checks tests · scan · review Merged when green

Configure branch protection on the default branch so the required checks and a review are mandatory. This can be done in the UI or with the API:

gh api -X PUT repos/:owner/:repo/branches/main/protection \
  --input - <<'JSON'
{
  "required_status_checks": {
    "strict": true,
    "contexts": ["verify", "dependency-review"]
  },
  "required_pull_request_reviews": { "required_approving_review_count": 1 },
  "enforce_admins": true,
  "restrictions": null
}
JSON

With contexts listing your CI test job and dependency-review job, gh pr merge --auto cannot complete until both pass. The required_approving_review_count of 1 is satisfied for patch and minor updates by the workflow’s own approval step, but a major update — which the workflow never approves — stays blocked until a human approves it.

Verification

Simulate both a patch and a major update and confirm each takes the expected path.

# 1. Confirm auto-merge is enabled on a live patch/minor PR
gh pr view <patch-pr-number> --json autoMergeRequest,reviewDecision
# Expected: autoMergeRequest is non-null (queued), reviewDecision "APPROVED"

# 2. Confirm a major PR is NOT queued and awaits review
gh pr view <major-pr-number> --json autoMergeRequest,reviewDecision
# Expected: autoMergeRequest is null, reviewDecision "REVIEW_REQUIRED"

# 3. Inspect the workflow run to see which steps executed
gh run view <run-id> --log | grep -E "Enable auto-merge|semver-major"
# Expected: for a major update, the "Enable auto-merge" step is skipped

# 4. Prove required checks gate the merge: temporarily fail a check
#    on a patch PR and confirm it stays open despite being queued.
gh pr checks <patch-pr-number>
# Expected: merge does not complete while any required check is failing

A correctly configured setup shows the patch PR queued and approved but merging only after every check is green, and the major PR neither approved nor queued, sitting in REVIEW_REQUIRED.

Troubleshooting

Symptom Likely cause Fix
Workflow fails with “Resource not accessible by integration” Using pull_request event, whose token is read-only for Dependabot Switch the trigger to pull_request_target and grant pull-requests: write and contents: write
Auto-merge enabled but PR never merges Repository setting “Allow auto-merge” is off, or no required checks are configured Enable auto-merge in Settings → Pull Requests and add required status checks in branch protection
Major updates are auto-merging The if condition is missing or matches semver-major Restrict the condition to version-update:semver-patch and version-update:semver-minor only
gh: command not found Self-hosted runner without the GitHub CLI Install gh on the runner, or run on a GitHub-hosted runner where it is preinstalled
Patch PR merges with tests failing --auto used but the test job is not a required check Add the test job’s check name to required_status_checks.contexts in branch protection

Common Implementation Mistakes

Frequently Asked Questions

Does gh pr merge --auto bypass required status checks?

No. The --auto flag queues the pull request to merge only after every required status check configured in branch protection has passed. If branch protection requires your CI and dependency-review checks, the merge waits for them to go green before completing. Auto-merge is a scheduling instruction, not an override — the enforcement lives entirely in branch protection. This is why Step 3 is mandatory: without required checks defined, --auto would merge as soon as the PR is approved, which for a patch happens immediately via the workflow’s own approval step.

Why use pull_request_target instead of pull_request for the workflow?

Dependabot pull requests run with a read-only GITHUB_TOKEN under the ordinary pull_request event, so a workflow triggered that way cannot approve the PR or enable auto-merge. The pull_request_target event runs in the context of the base branch and receives a token with write access, which is what the approval and merge steps need. Because that event is more privileged, never check out or execute untrusted PR code within it — restrict the job to calling fetch-metadata and gh, neither of which runs the dependency’s own code, so an attacker-controlled release cannot exploit the elevated token.

How do I stop major version bumps from auto-merging?

Gate the merge step on the update-type output from dependabot/fetch-metadata, allowing only version-update:semver-patch and version-update:semver-minor. A major update produces version-update:semver-major, which fails that condition, so both the approval and gh pr merge steps are skipped and the PR waits. Keep branch protection requiring at least one approving review; since the workflow never approves a major update, it cannot merge until a human signs off. For the broader reasoning on why majors carry the most risk, see the parent Automated Dependency Updates as Policy-as-Code guide.