Automated Dependency Updates Jump to heading
Dependency automation fails in one of two directions. Either nothing is configured and the repository accumulates two years of drift until a security advisory forces a weekend of upgrades, or a bot is switched on with defaults and opens forty pull requests a week until everyone mutes the notifications β at which point the outcome is identical to having no automation, with extra CI spend. The difference between the two failure modes and a working setup is almost entirely about batching, scheduling and where the trust boundary sits. This part of Git Automation & CI/CD Hook Engineering covers the design decisions that keep updates flowing.
Prerequisites Jump to heading
The Shape of the Problem Jump to heading
An update bot produces a stream of changes at a rate you choose. Each one costs a pipeline run, a review decision and a merge. Left at defaults, the rate is set by the ecosystem β and a mid-sized JavaScript project has enough transitive churn to generate several updates a day. The three levers that make the stream manageable are grouping, scheduling and automatic merging, and they interact: aggressive grouping makes auto-merge riskier, tight scheduling makes grouping more effective.
Step 1 β Choose a Schedule Before Choosing a Tool Jump to heading
A schedule is what turns a stream into a batch. Scanning continuously produces pull requests at random times, each one alone; scanning twice a week produces two batches that can be grouped.
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended"],
"schedule": ["after 2am and before 6am on monday and thursday"],
"timezone": "Europe/London",
"prConcurrentLimit": 5,
"prHourlyLimit": 2
} # Verification: dry-run the configuration without opening anything
npx --yes renovate --dry-run=full --platform=local 2>&1 | tail -30 The two limits matter as much as the schedule. Without a concurrency cap, the first run after enabling the bot opens every outstanding update at once, which is the experience that gets dependency automation switched off again.
Step 2 β Group Updates by Blast Radius, Not by Ecosystem Jump to heading
The instinct is to group by package manager. The useful grouping is by what a failure would mean: development tooling that cannot affect production, patch updates to runtime dependencies, and major versions that need reading release notes.
{
"packageRules": [
{
"matchDepTypes": ["devDependencies"],
"groupName": "dev tooling",
"automerge": true
},
{
"matchUpdateTypes": ["patch"],
"matchDepTypes": ["dependencies"],
"groupName": "runtime patches",
"minimumReleaseAge": "3 days"
},
{
"matchUpdateTypes": ["major"],
"groupName": null,
"automerge": false,
"labels": ["needs-release-notes"]
}
]
} # Verification: which group would a given package land in?
npx --yes renovate --dry-run=full --platform=local 2>&1 | grep -i 'groupName\|automerge' | head minimumReleaseAge deserves its own mention: it delays adoption by a few days so that a compromised or broken release has time to be pulled before your pipeline consumes it. It costs nothing and removes a whole class of supply-chain exposure β the same reasoning behind pinning GitHub Actions to a commit SHA.
Step 3 β Decide Where Automatic Merging Stops Jump to heading
Auto-merge is safe exactly to the extent that the pipeline would catch the failure. That is a statement about your test suite, not about the bot, and it should be written down as such.
# An honest test: would the suite catch a dependency that breaks at runtime?
npm install --no-save [email protected] 2>/dev/null
npm test >/dev/null 2>&1 && echo "suite passed β is that because nothing exercises it?" \
|| echo "suite fails fast, as it should"
git checkout -- package-lock.json SAFETY WARNING β automatic merging combined with a test suite that does not exercise a dependency is a silent supply-chain channel: a malicious or broken release merges to the default branch with nobody reading the diff. Restrict auto-merge to dependency classes your pipeline genuinely covers, require a release soak period, and keep the merge going through the same protected path as human changes.
Step 4 β Deal With Lockfile Conflicts Structurally Jump to heading
Bulk updates conflict with each other by construction, because every one of them rewrites the same lockfile. Resolving those conflicts by hand is wasted effort β the file is generated, and the correct resolution is always to regenerate it.
# gitattributes: stop Git trying to merge a generated file line by line
printf 'package-lock.json merge=ours linguist-generated=true\n' >> .gitattributes
git config merge.ours.driver true
# The real resolution: take the incoming branch and rebuild
git checkout --theirs package-lock.json && npm install --package-lock-only # Verification: a rebuilt lockfile is reproducible from the manifest alone
rm -f package-lock.json && npm install --package-lock-only && git diff --stat package-lock.json The details, including the equivalents for other ecosystems, are in keeping lockfiles conflict-free during bulk updates.
Step 5 β Measure the Backlog, Not the Activity Jump to heading
A dependency bot generates visible activity whether or not it is working. The number that matters is how long an update waits before merging.
# Age of open dependency pull requests, oldest first
gh pr list --label dependencies --json number,title,createdAt \
--jq 'sort_by(.createdAt) | .[] | "\(.createdAt[0:10]) #\(.number) \(.title)"' | head -20 Configuration Reference Jump to heading
| Setting | Typical default | Effect | When to change |
|---|---|---|---|
schedule | any time | When the bot may open pull requests | Set a window so updates batch |
prConcurrentLimit | 10 | Maximum open update pull requests | Lower it to a number the team can drain |
minimumReleaseAge | none | Delay before adopting a new release | Set 3β7 days for runtime dependencies |
groupName | per package | Which updates share one pull request | Group by consequence, not ecosystem |
automerge | false | Merge without a human on green | Enable only where the suite covers the risk |
rangeStrategy | auto | Whether to widen or pin version ranges | Pin for applications, widen for libraries |
lockFileMaintenance | disabled | Periodic full lockfile refresh | Enable monthly to stop transitive drift |
Troubleshooting Jump to heading
| Symptom | Likely cause | Fix |
|---|---|---|
| Dozens of pull requests on day one | No concurrency limit during onboarding | Set a low limit, raise it as the backlog drains |
| Update pull requests conflict constantly | Every branch rewrites the lockfile | Regenerate rather than merge; see the gitattributes rule |
| Auto-merged update broke production | The suite does not exercise that dependency | Narrow the auto-merge scope; add coverage |
| The bot keeps reopening a closed update | Closing is read as βnot nowβ, not βneverβ | Add an explicit ignore rule with a reason |
| Major versions never get merged | No scheduled slot for reading release notes | Book one, and label them so they are findable |
| Pipeline cost tripled | One run per update, none grouped | Group, and cancel superseded runs |
Rolling It Out Without a Flood Jump to heading
Switching a bot on for a repository that has never had one produces the backlog all at once. The order below spreads that first wave over a fortnight and lets the team calibrate as they go, rather than deciding everything up front and discovering the consequences on Monday morning.
The reason the ordering matters is psychological as much as technical. A team that sees forty pull requests on day one concludes that dependency automation is noise and never revisits the judgement; a team that sees three, merges them without incident, and then sees five learns that the bot is trustworthy and that its output is worth reading. The configuration ends up identical either way β what differs is whether anyone is still paying attention by the time it is finished.
One more decision belongs in this phase: who owns the stream. Dependency updates are nobodyβs feature and therefore nobodyβs job, which is how a backlog of ninety open update pull requests accumulates in a repository where everything else moves quickly. Naming an owner β a rota, a slot in a weekly routine, an explicit line in someoneβs responsibilities β is the difference between automation that keeps the repository current and automation that generates a queue for a future emergency. The measurement in the previous step is what that owner uses; a rising median wait is the signal that the capacity assumption has stopped holding.
Frequently Asked Questions Jump to heading
Is automatic merging of dependency updates responsible? Jump to heading
It is responsible for the classes where your pipeline is the reviewer β build tooling, type definitions, linters β and irresponsible where it is not. The question is never βdo we trust the botβ, it is βwould our tests fail if this update were wrongβ. Answer that per group and the policy writes itself.
Should applications pin exact versions? Jump to heading
Applications should pin and rely on the lockfile; libraries should express ranges so consumers can resolve one copy. Pinning in a library forces version conflicts onto everyone downstream, and widening in an application makes builds non-reproducible for no benefit.
How do we handle a dependency we deliberately will not upgrade? Jump to heading
Record the decision where the bot can read it: an ignore rule with a comment explaining why and what would change the answer. A closed pull request is not a decision, and the bot will correctly reopen it next week.
What about transitive dependencies nothing updates? Jump to heading
That is what periodic lockfile maintenance is for. Direct dependency bumps do not refresh the rest of the tree, so a monthly full refresh is the only thing that stops transitive versions ageing quietly for years.
Should the bot open pull requests against release branches too? Jump to heading
Only if you actively maintain those branches, and then with a much narrower policy: security fixes and nothing else. A release branch exists to change as little as possible, so routine updates there defeat its purpose while adding merge work to every backport. Configure the branch list explicitly rather than letting the bot discover branches, or a stale release line from two years ago will start receiving weekly pull requests nobody wants.
Related Jump to heading
- Configuring Renovate for a Monorepo β one bot, many manifests, without a pull request per package.
- Grouping Dependency Updates to Reduce PR Noise β the grouping rules that actually cut the volume.
- Auto-Merging Patch Updates Safely β where the trust boundary belongs.
- Keeping Lockfiles Conflict-Free During Bulk Updates β regenerating instead of merging.
- Pinning GitHub Actions to a Commit SHA β the same discipline applied to the pipeline itself.