Auto-merging patch updates safely Jump to heading

Automatic merging is delegation, and delegation is only sound when the delegate can do the job. Here the delegate is your pipeline, so the question is not whether bots can be trusted but whether your tests would fail if a given dependency were subtly wrong. Answer that honestly and the policy follows: auto-merge where the pipeline is a real reviewer, require a human where it is not. This recipe draws that boundary and adds the safeguards around it, within automated dependency updates.

When to use this approach Jump to heading

  • Update pull requests are merging late or not at all because nobody has time.
  • Your suite genuinely exercises the dependencies you intend to auto-merge.
  • Branch protection is in place, so an automatic merge still passes every gate.
  • You can detect and revert a bad merge quickly.
  • If your tests are thin or flaky, fix that first — auto-merge on a weak suite is a supply-chain channel, not a time saving.

Step 1 — Establish what the pipeline actually covers Jump to heading

Coverage numbers do not answer this. The question is whether a wrong version of a specific dependency causes a failing test.

# Pick a dependency and break it deliberately in a scratch clone
git worktree add /tmp/covtest HEAD && cd /tmp/covtest
npm install --no-save [email protected]     # a deliberately ancient major
npm test >/dev/null 2>&1 \
  && echo "NOT covered: the suite passed with the wrong version" \
  || echo "covered: the suite caught it"
cd - && git worktree remove /tmp/covtest --force

Run that for each dependency class you are considering. The result is a list, and the list — not a general policy — is what goes into the configuration.

Two dependencies, two answersA date library is exercised by hundreds of assertions, so a wrong version fails immediately and the pipeline is a genuine reviewer. A logging transport is called but never asserted on, so an incompatible release passes every test and reaches production unread.Date libraryLogging transportexercised by testshundreds of assertionscalled, never assertedwrong version detectedimmediatelyneverauto-mergedefensiblenot defensibleneeded changenoneadd a contract testthe boundary is per dependency, not per ecosystem

Step 2 — Add a soak period before adopting a release Jump to heading

Most bad releases are discovered within days by someone else. Waiting costs nothing and removes the window in which you are the one who finds out.

{
  "packageRules": [
    {
      "matchDepTypes": ["dependencies"],
      "matchUpdateTypes": ["patch"],
      "minimumReleaseAge": "5 days",
      "automerge": true,
      "automergeType": "pr",
      "platformAutomerge": true
    }
  ]
}
# Verification: no update younger than the soak window should be open
gh pr list --label dependencies --json title,createdAt \
  --jq '.[] | select(.createdAt > (now - 5*86400 | todate)) | .title'

platformAutomerge matters: it asks the forge to merge when checks pass, rather than having the bot push a merge itself. The merge then goes through branch protection exactly like a human merge, which is the property you want.

Step 3 — Keep the automatic merge on the protected path Jump to heading

An automatic merge that bypasses protection is worse than no automation, because it creates a route to the default branch that is exempt from your rules.

# The bot's identity must NOT be in any bypass list
gh api repos/:owner/:repo/rulesets --jq '.[].bypass_actors[]?
  | {actor_id, actor_type, bypass_mode}'
# Verification: an auto-merged update should show the same required checks as any PR
gh pr view --json number,mergedBy,statusCheckRollup \
  --jq '{by: .mergedBy.login, checks: [.statusCheckRollup[].name]}'

SAFETY WARNING — never grant a dependency bot bypass rights “so auto-merge works”. If auto-merge needs a bypass, something in your protection is misconfigured; find it. A bot with bypass rights is an unattended write path to your default branch, and its credentials live in the forge’s configuration rather than in anyone’s head.

An automatic merge that still respects every gateThe bot opens the update and marks it for automatic merging. The forge runs the required checks, waits for them to pass, and then performs the merge itself under the same protection rules a person would face. The bot never pushes to the default branch.update botpull requestrequired checksdefault branchopen and flag for auto-mergerun the required setall greenforge merges under protectionbot never pushes herethe bot asks; the forge decides — which is what keeps protection meaningful

Step 4 — Make a bad merge cheap to undo Jump to heading

Automation changes the recovery question from “who broke this” to “how fast can we put it back”. Both answers should be ready before you switch it on.

# Find the automatically merged commits since a known-good point
git log --merges --format='%h %an %s' v2.4.0..HEAD | grep -i 'renovate\|dependabot'

# Revert one cleanly, keeping the history honest about what happened
git revert -m 1 <merge-sha>
npm install --package-lock-only && git add package-lock.json
git commit --amend --no-edit
# Verification: the lockfile after the revert matches the manifest
npm ci --dry-run >/dev/null && echo "lockfile and manifest agree"

The choice between reverting and rolling forward is covered in when to use git revert vs git reset; for an automatic merge on a shared branch, revert is almost always right.

Step 5 — Build the kill switch and test it Jump to heading

There will be a week when auto-merge has to stop immediately — an incident, a release freeze, a compromised registry.

{
  "enabledManagers": ["npm"],
  "packageRules": [
    { "matchUpdateTypes": ["patch"], "automerge": true }
  ],
  "prCreation": "immediate"
}
# The switch: one commit that disables merging without disabling updates
jq '.packageRules |= map(.automerge = false)' renovate.json > renovate.json.tmp \
  && mv renovate.json.tmp renovate.json
git commit -am "chore: pause dependency auto-merge during the release freeze"
# Verification: the next run opens pull requests but merges none
gh pr list --label dependencies --json number,autoMergeRequest \
  --jq '[.[] | select(.autoMergeRequest != null)] | length'
What to do when an automatically merged update breaks somethingIf the default branch is broken, revert the merge commit first and investigate afterwards. If only a deployment is affected, roll back the deployment and keep the commit. In both cases the automation is paused until the cause is understood.What is broken right now?the default branchRevert the mergethen pause auto-mergeonly the deploymentRoll back the deploykeep the commitnothing yetPin the versionand raise an issuedecide this once, in advance — not at the moment it happens

Validation checklist Jump to heading

Frequently Asked Questions Jump to heading

Can we auto-merge minor updates as well as patches? Jump to heading

Only where the dependency’s own discipline about semantic versioning is good, and where your tests would notice. Minor releases add behaviour, and added behaviour occasionally changes defaults. A longer soak period and a narrower package list make it defensible for a small set of well-run dependencies.

Does auto-merge interact badly with a merge queue? Jump to heading

No, it composes well: the bot marks the pull request for automatic merging, the queue tests it against the current default branch, and it lands only if the candidate tree is green. If anything, a queue makes auto-merge safer, because the tested tree is the tree that lands.

What about updates to the pipeline’s own actions? Jump to heading

Treat them as production dependencies, because they run with your runner’s credentials. Pin them by commit id and review upgrades by hand — the reasoning is in pinning GitHub Actions to a commit SHA.