Keeping your code’s dependencies up to date is hard, so luckily there are free tools like Dependabot around, that can create PRs against your repository whenever any of your dependencies have a more recent version updated. Depending on the size of your codebase, this can turn into quite a chore as you have to verify that the dependencies don’t break your code.

A common solution is to establish a thorough test suite and other CI checks that you trust to let you know if your code is broken and combine that with automatically merging Dependabot PRs.

Unfortuately, the default instructions from GitHub for doing exactly this leave a little to be desired:

They recommend creating a separate GitHub Actions workflow, like the following:

name: Dependabot auto-merge
on: pull_request

permissions:
  contents: write
  pull-requests: write

jobs:
  dependabot:
    runs-on: ubuntu-latest
    if: ${{ github.actor == 'dependabot[bot]' }}
    steps:
      - name: Dependabot metadata
        id: metadata
        uses: dependabot/fetch-metadata@v1.1.1
        with:
          github-token: "${{ secrets.GITHUB_TOKEN }}"
      - name: Enable auto-merge for Dependabot PRs
        if: ${{contains(steps.metadata.outputs.dependency-names, 'my-dependency') && steps.metadata.outputs.update-type == 'version-update:semver-patch'}}
        run: gh pr merge --auto --merge "$PR_URL"
        env:
          PR_URL: ${{github.event.pull_request.html_url}}
          GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}

If you just apply this naively, patch version upgrade Dependabot PRs for a fictitious my-dependency dependency will be merged automatically without waiting for any particular jobs (e.g. your tests) to finish. That’s probably not what you want.

Instead you probably want your standard CI jobs like testing and linting to pass first, and it’s more reasonable to expect those jobs to run on push instead of pull_request or pull_request_target. This becomes a problem since the steps mentioned need pull_request or pull_request_target events to work.

After a lot of trial and error, I’ve come up with an alternative approach I prefer instead, and I’m sharing it here in case others can benefit from this as well:

on:
  push: (...)

permissions:
  contents: write
  pull-requests: write

jobs:
  # Verify you can build the code
  build: (...)
  # Run a linter against your code
  lint: (...)
  # Run your tests against the code
  test: (...)

  # Automatically merge if it's a Dependabot PR that passes the build
  dependabot:
    runs-on: ubuntu-latest
      # Only run this job for dependabot PRs
      if: ${{ github.actor == 'dependabot[bot]' }}

      # Only run if the required checks pass
      needs: [test, lint, build]

      steps:
        - name: Check out code
          uses: actions/checkout@v3

        - name: Auto-merge Dependabot PRs
          # Find the PR number based on the current branch name, and squash merge based on this number
          run: 'PR_NUM="$(gh pr list | grep $(git branch --show-current) | cut -f1)"; gh pr merge --auto --squash $PR_NUM'
          env:
            GH_TOKEN: ${{secrets.GITHUB_TOKEN}}

The above will automatically merge your Dependabot PRs, and only your Dependabot PRs, if the build passes. You may have noticed that I haven’t included the check that would only run this for only patch updates – that’s because I personally don’t want this check. I leave adding this check as an exercise for the reader.

Important note

Do this only if you trust your dependencies. This automation makes you particularly vulnerable to supply-chain attacks.