Dependency upgrade automation across repos
Automate dependency upgrades across hundreds of repos: find every affected version, open PRs everywhere, and track them to merge with Sourcegraph Batch Changes.

Automate dependency upgrades across hundreds of repos: find every affected version, open PRs everywhere, and track them to merge with Sourcegraph Batch Changes.
A CVE drops on a Friday afternoon. If the affected package lives in one service, you bump a version, run CI, and merge. If it lives in 300 services owned by nine teams, you have a project on your hands. Dependency upgrade automation is supposed to make that second case disappear, and for a single repo, it mostly does. The hard part starts when "update this dependency" means "update it in every repo that pins it, and don't lose track of a single pull request."
This post is for engineers and platform teams who already run Dependabot or Renovate and keep hitting the same wall: per-repo bots are great at one repo at a time, and the moment a change has to land everywhere at once, you're back to spreadsheets. We'll cover what those bots do well, where they stop, and how to handle the cross-repo case by finding every affected version with Code Search and rolling out one coordinated change with Batch Changes.
Keeping dependencies current is the part of dependency management everyone agrees matters and almost no one prioritizes, and the data shows it. The Black Duck 2026 OSSRA report, which audited 947 commercial codebases, found that 92% of codebases contained components four or more years out of date, and only 7% of components in use were the latest version. Most open-source dependency trees are stale, and that staleness compounds quietly until a forced upgrade turns into an archaeology project.
The pain isn't the single bump; it's the coordination. Upgrading Lodash in one repo is a two-minute job. Upgrading it across every repo that depends on it means first answering a question most development teams can't answer quickly: which repos use it, and which version each repo pins? In a monorepo, you can grep. Across a few hundred separate repositories on GitHub, GitLab, and a Bitbucket instance left over from an acquisition, grep doesn't reach.
Deferring upgrades makes it worse. Wait long enough that you have to jump several major versions at once to pick up a security patch, and the blast radius of breaking changes balloons. The same OSSRA audit found open source vulnerabilities per codebase rose 107% to an average of 581, with 87% of codebases carrying at least one. Every deferred upgrade is a future incident with interest.
The problem decomposes into three jobs: find where a dependency lives and at what version, make the change everywhere, and track every resulting pull request to merge. Per-repo automation nails the first two for one repo.
For dependency updates inside a single repo, these are the right two tools. Both watch your manifests, check for newer versions, and open a pull request so you can review and merge.
Dependabot keeps packages current and opens automated pull requests for both version and security updates, the latter for dependencies with known vulnerabilities. You enable it by checking a dependabot.yml file into the repository, and it decides whether to bump a dependency by reading its semantic version. It's the path of least resistance if your code already lives on GitHub.
Renovate covers more ground on configuration and platforms. It automates dependency updates across many platforms and languages, opening PRs to update dependencies and lock files, and you can schedule when it raises PRs and let it auto-detect package files, including inside monorepos. When a PR meets the conditions you define, Renovate can merge it automatically with no human in the loop, configured through packageRules in renovate.json. Mend Renovate is AGPL-3.0-licensed and can be used as an npm package, a container image, or via the hosted Mend Renovate App.
Both share one boundary: their unit of work is the repository. Dependabot's config lives in each repo; Renovate runs against each repo's package files and opens PRs there. That's exactly right for "keep this repo's dependencies fresh." It stops being enough for "apply one specific upgrade across 200 repos and watch all 200 PRs as a single rollout." Neither bot is a cross-repo control plane, and asking them to be one is what leads development teams to fall back on manual tracking.
| Capability | Dependabot | Renovate | Sourcegraph Batch Changes |
|---|---|---|---|
| Unit of work | One repository | One repository | A set of repos matched by one query |
| How it's triggered | Notices new versions automatically | Notices new versions, on a schedule | One intentional change you author and preview |
| Config location | dependabot.yml per repo |
renovate.json per repo |
One batch spec for the whole rollout |
| Auto-merge | Yes (per repo, conditional) | Yes (per repo, conditional) | Yes, GitHub code hosts, admin-restrictable |
| Cross-repo tracking | No single cross-repo view | No single cross-repo view | One dashboard for every changeset |
The work that breaks per-repo automation is what we call the outer loop: reviewing, securing, and rolling out changes across many repositories at once. A bot that opens a PR in each repo on its own schedule is solving the inner loop one repo at a time. The outer loop is different in three ways.
renovate.json governing the set.This is the case Sourcegraph is built for: treat a cross-repo upgrade as one coordinated rollout, not 200 manual ones. The workflow is to enumerate, change, and track. The next three sections walk through each step.
Before you change anything, you need an honest inventory: not "which repos probably use this package," but every repo, every manifest, and the exact version each one pins. That's the difference between a clean rollout and discovering three forgotten services after you thought you were done.
Code Search runs literal, keyword, and regex searches across an organization's codebases, from 100 to over a million repositories. A regex query against package.json, go.mod, pom.xml, or requirements.txt returns the version string each repo pins across GitHub, GitLab, Bitbucket, Gerrit, and Perforce. A Perforce depot from an acquisition appears alongside your GitHub org in a single results set. For deeper questions, such as the call sites of a function whose signature is about to change, navigation uses SCIP-based semantic analysis with cross-repository symbol resolution.
The payoff is concrete. When Quantcast needed to refactor across a sprawling codebase, a single engineer used multi-repository search to analyze thousands of repositories in a few days, work that would otherwise have taken months. That same repositoriesMatchingQuery that finds the repos becomes the targeting rule for the change itself.
Once you know where a dependency lives, Batch Changes applies the upgrade everywhere from one declarative file. That file is a batch spec: a YAML file that defines which changes to make and which repositories to make them in. The on.repositoriesMatchingQuery field takes a Sourcegraph search query, and every repository the query matches joins the set the change runs on, so you can reuse the enumeration query from the previous step.
Each step runs a shell command in a container against every matched repository, and the changesetTemplate describes the pull request (or merge request) opened on each code host. Below is an example of the config needed to run an update script with old and new version variables:
version: 2
name: bump-acme-lib-1.33
description: Upgrade acme-lib from 1.31.7 to 1.33.0 across all repos that pin it
# Reuse the enumeration query: every repo whose package.json pins acme-lib
on:
- repositoriesMatchingQuery: file:package.json acme-lib patterntype:keyword
# Run the upgrade in a container, per matched repo
steps:
- run: ./update_dependency.sh
container: our-custom-image
env:
OLD_VERSION: 1.31.7
NEW_VERSION: 1.33.0
# Describe the PR each repo gets
changesetTemplate:
title: Upgrade acme-lib to 1.33.0
body: Coordinated upgrade of acme-lib. CI must pass before merge.
branch: deps/acme-lib-1.33
commit:
message: 'Upgrade acme-lib 1.31.7 to 1.33.0'
published: false
The spec above is the deterministic path, and for a uniform change, such as a version bump, it is usually all you need. Additionally, we've also added Agentic Batch Changes (in Beta) to Sourcegraph, which puts an orchestration agent in front of the same workflow. That outer-loop agent gets two tools: run a deterministic script or hand a repo off to an inner-loop coding agent like Claude Code or Codex for changes that need per-repo judgment. It scripts the parts it can predict and only pulls in coding agents for edge cases, and every change is still previewed and tracked for merge in the same way. The declarative spec handles clean upgrades; the agentic path earns its keep when the fix varies across repos.
This is where the per-repo and cross-repo models visibly diverge. A bot opens one PR per repo whenever it notices an update. Here, a single declarative file opens the same intentional change across every repository the query matched, in one operation you previewed first.
Opening 200 pull requests is the easy 20%. Getting all 200 merged while CI churns and merge conflicts appear in repos that moved under you is the other 80%, and it's where manual tracking falls apart.
Batch Changes gives each changeset its own dashboard. You can import and track changesets that already exist across different code hosts and run bulk actions on them: comment on each, merge each to main, or close each, so even PRs a per-repo bot opened can sit in the same view as the ones your batch change created. When repos drift and a changeset goes stale, you can bulk-rebase changesets to keep them merge-ready, instead of rebasing each branch by hand.
If using auto-merge, there are a few important notes to keep in mind. Batch Changes supports auto-merge for GitHub code hosts, so a changeset can merge itself once its required checks pass. That's worth gating carefully. We even added a site config option, batchChanges.restrictMergeToAdmins, to restrict merge and auto-merge actions to site admins only, which is the control you want before you let a few hundred dependency PRs merge themselves. Auto-merge is GitHub-only today; on other code hosts, you merge through the same dashboard, with a human clicking the bulk action.
Most dependency upgrades can wait a sprint. A disclosed CVE in an open-source package you ship can't, and the at-scale version of that emergency is what separates development teams with a coordinated workflow from teams pulling an all-nighter. When the average per codebase is 581 vulnerabilities, you will run an emergency cross-repo upgrade sooner or later, so the only real question is whether your tooling is ready when the pager goes off.
The enumerate, change, track loop runs the same under pressure, only faster. Code Search finds every repo that pinpoints the vulnerable open-source version, including the ones nobody remembers, in a single query. Batch Changes opens the patched changeset everywhere from one spec, published as a draft so CI vets the bump before anything is mergeable. Then the dashboard plus GitHub auto-merge clear the safe ones automatically while you focus on the repos where the patch actually broke something.
Two recent additions matter for security-sensitive rollouts. Within Batch Changes, we've added fine-grained access tokens scoped to specific repositories and GitLab- and GitHub-style signed commits, so an emergency patch is both least-privilege and provably authored. That traceability is why Workiva's Director of Architecture says Batch Changes gives the team "the confidence we need to understand the total impact of large-scale code changes before we make them." We'll go deeper into automated security remediation in a follow-up.
To see how a single declarative change applies across every repo you own, and how to track every changeset to merge in a single view, take a look at how teams run a migration as a coordinated rollout. That single view is what stands between you and having to chase 200 PRs by hand.
Renovate vs Dependabot: which should I use for a single repo? Both work. Dependabot is the lowest-friction option if your code is on GitHub, a dependabot.yml away, and covering version and security updates out of the box. Renovate is more configurable, runs on more platforms, and gives finer control over scheduling and auto-merge rules. Neither is the right tool for coordinating one upgrade across many repos at once.
Can I automate a dependency upgrade across many repositories at once? Yes, but not with a per-repo bot. Cross-repo dependency management means enumerating every affected repo and version, applying one change across all of them, and tracking the resulting pull requests in one place. That's the Code Search plus Batch Changes workflow: one batch spec targets every matched repo, opens a changeset in each, and surfaces all of them in a single dashboard.
Does this work with monorepos? Yes. Renovate auto-detects package files inside monorepos for the per-repo case. For the cross-repo case, a batch spec can target workspaces within a repository, for example, every directory containing a package.json, creating a separate changeset per project, so a monorepo is handled alongside your standalone repos.
Is auto-merging dependency PRs safe? It's safe when it's gated. Make merges conditional on required CI checks, publish as draft so nothing is mergeable until it's vetted, and restrict who can enable merge and auto-merge with batchChanges.restrictMergeToAdmins. Auto-merge is for the boring green PRs; the ones that fail CI stay open for a human to review.

With Sourcegraph, the code understanding platform for enterprise.
Schedule a demo