Forty pull requests from a bot sit open, one package each, because nobody has merged one in a month. Then someone merges them all in an afternoon, including the release that reached the registry that morning. The update queue is either ignored or trusted wholesale, and an SDK repository with four languages produces four queues.
What you get
You will end up with a dependabot.yml and a renovate.json that state one policy, a merge rule
naming the updates that need no person, and a script proving the two files agree. This is for you
if you maintain an SDK in several languages and the update queue has become noise.
Short answer
Let the bot open the pull requests and let a release-age delay decide when: three days by default,
seven for a minor, fourteen for a major. Automerge patches of stable dependencies alone, and only
once the test suite calls the code that changed. Dependabot needs a workflow to merge; Renovate has
automerge in its config. Everything else waits for a person.
You will need
A repository on GitHub, or on any of the hosts
Renovate runs against. A lockfile in every
language you ship, so a bump is one reviewable diff and one clean revert. A test suite that calls
the dependencies it claims to cover, which the last section of this page measures. Node 22 or
later for the checking script. Verified 2026-09-24 against Node 22.22.2 and yaml 2.9.1.
Approaches compared
| Approach | When it fits | What it costs you | When to pick something else |
|---|---|---|---|
| Dependabot | The repository is on GitHub and you want nothing to install or host | No automerge of its own, so the merge decision is a workflow you write, and groups are keyed on name patterns, dependency type and semver level | The repository is on GitLab, or a rule needs to look at more than the version number |
| Renovate | Any host, a long list of package managers, and rules keyed on update type, dependency type, and manager | A configuration reference of several hundred options, and a bot you run yourself or a hosted app you install | The repository is on GitHub and the two-file policy on this page is all you need |
| Security updates only | A library with few dependencies, bumped by hand at each release | Drift between releases, and a hand upgrade that touches ten packages at once when a release is due | Dependencies change faster than you release |
The split is between a bot GitHub runs for you and a bot you configure in depth. Dependabot costs one file and offers one set of levers per ecosystem: a cooldown, a schedule, and groups by name, dependency type and semver level. Renovate costs a larger file and gives you rules that combine update type, dependency type, and manager, on any host. Security updates alone cost nothing and leave every other bump to release day.
Wait before you update
A release-age delay is the part of the policy that protects you, and it is the part that gets switched off first. Dependabot applies a cooldown of three days to every version update even when the file says nothing, and lets you lengthen it by semver level.1 The delay applies to version updates only. A security update goes out as soon as the advisory does.
version: 2
updates:
- package-ecosystem: npm
directory: /ts
schedule:
interval: weekly
day: monday
cooldown:
default-days: 3
semver-minor-days: 7
semver-major-days: 14
groups:
dev-tooling:
dependency-type: development
update-types: [minor, patch]
runtime-patches:
dependency-type: production
update-types: [patch]
open-pull-requests-limit: 10
One entry per ecosystem, and the SDK has four, so the file repeats itself four times. The semver
overrides are supported for npm, pip, and Go modules and not for GitHub Actions, which get
default-days alone. The groups block folds every development minor and patch into one pull
request a week, which is the difference between forty open pull requests and four. The file has
a schema on SchemaStore, so an editor can check
it before Dependabot does.
Renovate spells the same delay
minimumReleaseAge, once at the
top level and again inside any rule that needs a longer one.
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended"],
"minimumReleaseAge": "3 days",
"packageRules": [
{
"matchUpdateTypes": ["minor"],
"minimumReleaseAge": "7 days"
},
{
"matchUpdateTypes": ["major"],
"minimumReleaseAge": "14 days"
},
config:recommended brings the dependency
dashboard, the curated monorepo groupings, and the age and confidence badges on each pull request.
Both bots wait. Dependabot does not open the pull request until the cooldown has passed.
Renovate, under its default internalChecksFilter of strict, creates neither branch nor pull
request until the age is reached, whatever prCreation says.
Decide what merges without a person
Dependabot has no automerge setting. GitHub’s own
recipe
is a workflow that reads the pull request’s metadata and asks gh to merge it once the required
checks pass. The one below says yes to a patch of a stable dependency and to nothing else.
jobs:
dependabot:
runs-on: ubuntu-latest
if: github.event.pull_request.user.login == 'dependabot[bot]'
steps:
- name: Read what Dependabot changed
id: metadata
uses: dependabot/fetch-metadata@v2
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
# A grouped pull request carries several dependencies and `new-version`
# names only the first, so every version in the pull request is read.
- name: Check that no dependency in the pull request is 0.x
id: stable
run: echo "all=$(jq 'all(.[]; .newVersion | startswith("0.") | not)' <<< "$UPDATED")" >> "$GITHUB_OUTPUT"
env:
UPDATED: ${{ steps.metadata.outputs.updated-dependencies-json }}
- name: Merge a patch of stable dependencies once the checks pass
if: steps.metadata.outputs.update-type == 'version-update:semver-patch' && steps.stable.outputs.all == 'true'
run: gh pr merge --auto --squash "$PR_URL"
env:
PR_URL: ${{ github.event.pull_request.html_url }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
gh pr merge --auto merges when every required check has passed, so the branch protection rule
that requires the test suite is part of this policy and not a separate decision. Without that
rule the merge happens the moment the workflow runs. The jq step exists because
semver says a 0.x package may change anything at any time, so a 0.x
“patch” is a word rather than a promise. It reads every dependency, because a grouped pull
request carries several and new-version names only the first.
Renovate puts the same decision in the file that holds everything else, as
automerge on a rule. A major is left to a
person under both tools, which is the one decision nobody argues with.2
{
"matchUpdateTypes": ["patch"],
"matchCurrentVersion": "!/^0/",
"automerge": true
},
{
"matchDepTypes": ["devDependencies"],
"matchUpdateTypes": ["minor", "patch"],
"matchCurrentVersion": "!/^0/",
"automerge": true
},
{
"matchManagers": ["github-actions"],
"matchUpdateTypes": ["minor", "patch", "digest"],
"matchCurrentVersion": "!/^v?0/",
"automerge": true
}
]
}
Rules apply in order and a later match overrides an earlier one. So the file reads top to bottom
as a policy: patches of stable dependencies, development minors, and action bumps merge, unless
the dependency is at 0.x. matchCurrentVersion with a negated pattern is how the Renovate documentation excludes 0.x,
and the action rule allows for the v most tags carry, because Renovate matches the tag as written. Renovate refuses to automerge until every status check has passed, and the only way past that is
ignoreTests, which is named so that nobody sets it by accident.
Check it worked
policy.mjs reads the three files above with a YAML parser and applies their documented rules to
a table of updates. It covers the five match keys renovate.json uses and nothing more, so it is a
reading of the files rather than a copy of either bot.
node demo.mjs
update type age Dependabot Renovate
undici 6.20.1 -> 6.20.2 patch 1d holds 2d more holds 2d more
undici 6.20.1 -> 6.20.2 patch 4d merges itself merges itself
undici 6.20.1 -> 6.21.0 minor 4d holds 3d more holds 3d more
undici 6.21.0 -> 7.0.0 major 30d waits for a human waits for a human
typescript 5.6.2 -> 5.7.0 dev minor 10d waits for a human merges itself
yaml-lint 0.4.1 -> 0.4.2 patch (0.x) 10d waits for a human waits for a human
requests 2.32.3 -> 2.32.4 patch 4d merges itself merges itself
actions/checkout 4.2.1 -> 4.2.2 patch 4d merges itself merges itself
Seven of the eight rows agree, and the fifth is the shape of the difference between the tools. A
development minor merges itself under Renovate because one rule says so. Under Dependabot the
preceding workflow looks at the update type alone, so the same bump waits for a person until you
extend the condition with the dependency-type output that fetch-metadata also provides. The test file pins the properties the page promises.
node --test policy.test.mjs
1..6
# tests 6
# suites 0
# pass 6
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 137.052934
Test one: a major never merges itself under either bot, however old the release. Test two: nothing merges on the day it is released. Test three: a 0.x patch waits for a person. Test five lists the rows where the two files disagree and fails if that list changes. An edit to either file that opens a gap between them then fails the build rather than the release.
When it goes wrong
The check is green and the dependency is broken. The test suite never called the code that changed, so the merge rule was fed a color with nothing behind it. Two suites below run against a padding helper at 1.0.0 and at a patch release that flips the alignment.
node pitfall.mjs
suite pad 1.0.0 pad 1.0.1 (the patch)
shallow.test.mjs green green
deep.test.mjs green red
The shallow suite tests the repository’s own arithmetic and imports nothing from the dependency, so it stays green while every statement line comes out left-aligned. Before enabling automerge, list the dependencies the policy will merge and find the test that calls each one. A dependency with no such test is a dependency the rule has to exclude.
The pull request Renovate opened does not merge, even though its checks are green. Renovate merges one branch per target branch per run, and only when the branch is up to date with its base. Its documentation asks for about two hours before you call it a fault. If it still has not merged, look for a required review in the branch protection rules. Renovate cannot approve its own pull request.
Dependabot grouped a patch with a minor and the workflow did not merge it. The update-type
output reports the highest semver change in the pull request, so a group carrying one minor is a
minor. That is correct behaviour. If you want the patches merged on their own, give them their own
group, as runtime-patches does in the file shown earlier.
When not to do this
Do not switch on automerge before measuring what the suite exercises. A green check on a suite that never imports the dependency is a green check on nothing, and the merge rule cannot tell the difference.
Do not automerge a 0.x dependency, whatever the bump is called. The version number promises nothing below 1.0.0, and both configurations on this page carry a guard for exactly that reason.
Do not set the cooldown to zero to stay current. The three days are the window in which someone else reads the package, files the advisory, and gets the release pulled.3 A repository that installs a release the hour it is published has volunteered to be that someone.
Do not run both bots on one repository. Each opens its own pull requests for the same bumps, each rebases the other’s branches, and the queue you meant to shorten doubles.
Do not expect the cooldown to hold a security update. It applies to version updates only, and that is right: the advisory is the review.
Related how-tos
Last verified
Verified 2026-09-24 against Node 22.22.2 and yaml 2.9.1. Every output block is what the command
preceding it printed. Neither bot was run against a repository. policy.mjs reads the two
configuration files and the workflow and applies their documented rules, so it checks that the
files agree. It stands in for neither bot. Validate the files themselves with
the config validator for Renovate and the Dependabot
schema before relying on them.
Footnotes
-
The option was called
stabilityDaysbefore it wasminimumReleaseAge, and the documentation for Renovate allows that other ecosystems may call the same thing a dependency cooldown, which is what Dependabot calls it. Three names for one number, and the number is the only part either bot enforces. ↩︎ Back to text -
The reference for the options of Renovate carries a banner recording that Renovate 44 was released as a major version by accident, the changes being non-breaking. A tool whose work includes holding every major for a person shipped one that no person needed to read. The banner is the sort of thing a fourteen-day wait exists to let you find. ↩︎ Back to text
-
GitHub’s account of the Shai-Hulud worm says it was notified on September 14, 2025 and removed more than 500 packages. Dependabot waits three days by default. The example in the documentation for Renovate is fourteen, chosen to give researchers and scanners time to notice. Neither page derives its figure, and a delay of any length is a bet that somebody else reads the package first. ↩︎ Back to text