Description of the feature request
Problem
When using multiple --registry flags, Bazel merges available versions from all registries and MVS selects across the combined pool. There is no way to make an internal registry authoritative for specific modules.
This means an internal registry with patched or pinned module versions can be silently bypassed when BCR (or another registry) offers a higher version that a transitive dependency requests.
Concrete example
# .bazelrc
common --registry=file:///path/to/internal/registry/
common --registry=https://bcr.bazel.build
- Internal registry has
websocketpp@0.8.2 (patched, no boost.asio dep)
- BCR has
websocketpp@0.8.2.bcr.5 (unpatched, depends on boost.asio@1.89.0)
- A transitive dependency requests
websocketpp >= 0.8.2
- Expected: internal registry is listed first, so
websocketpp@0.8.2 is used
- Actual: MVS picks
0.8.2.bcr.5 from BCR (higher version in the merged pool), ignoring the internal registry entirely. The patch is silently lost.
This was independently confirmed by @guw in #25722:
"As a workaround we have an internal registry offering patched versions. But that is brittle. As soon as a newer (higher) version is available in BCR and consumed by a dependency in the graph, the patched version is lost, because even single_version_override is limited to the root module only."
Observed behavior vs documented behavior
The --registry flag documentation states:
"The order is important: modules will be looked up in earlier registries first, and only fall back to later registries when they're missing from the earlier ones."
In practice, this is not enforced per-module. Versions from all registries are merged into a single pool before MVS runs. A module present in the first registry can still be resolved from a later registry if a higher version exists there.
Why this matters
Organizations maintaining internal registries with patched dependencies have no reliable way to ensure their patches are used. The only workaround today is forking the entire BCR and using a single --registry flag, which is a significant maintenance burden.
Related: #25722 - How to patch dependencies of non-root module in bzlmod
Proposal: --registry-exclusive flag
Add a new flag --registry-exclusive=<registry_url> that marks a registry as authoritative for any module it contains.
Behavior
When a registry is marked exclusive:
- During module resolution, Bazel checks if the exclusive registry has a
metadata.json for the module
- If yes, only versions from that registry are considered by MVS. All other registries are skipped for that module.
- If no, resolution proceeds normally across all registries (fallthrough)
Usage
# .bazelrc
common --registry=https://internal.example.com/registry/
common --registry=https://bcr.bazel.build
common --registry-exclusive=https://internal.example.com/registry/
With the example above:
websocketpp exists in the internal registry -> only 0.8.2 is available -> MVS picks 0.8.2 -> patch is applied
rules_pkg does not exist in the internal registry -> falls through to BCR -> resolved normally
Why this design
- Backward compatible: default behavior is unchanged;
--registry-exclusive is opt-in
- Minimal surface area: single new flag, no changes to
MODULE.bazel syntax or registry format
- Composable: multiple registries can be marked exclusive, each authoritative for its own set of modules
- Predictable: if a module is in an exclusive registry, it can never be silently overridden by a higher version from another registry
Impact
This would unblock organizations that:
- Maintain internal registries with patched modules (security fixes, compatibility patches, platform-specific changes)
- Ship libraries as non-root Bazel modules that require specific dependency versions
The current workarounds (forking BCR, version bumping tricks, adding transitive deps as direct deps) are all brittle and high-maintenance.
Which category does this issue belong to?
Core
What underlying problem are you trying to solve with this feature?
No response
Which operating system are you running Bazel on?
Linux 22.04
What is the output of bazel info release?
release 7.4.0
If bazel info release returns development version or (@non-git), tell us how you built Bazel.
No response
What's the output of git remote get-url origin; git rev-parse HEAD ?
Have you found anything relevant by searching the web?
No response
Any other information, logs, or outputs that you want to share?
No response
Description of the feature request
Problem
When using multiple
--registryflags, Bazel merges available versions from all registries and MVS selects across the combined pool. There is no way to make an internal registry authoritative for specific modules.This means an internal registry with patched or pinned module versions can be silently bypassed when BCR (or another registry) offers a higher version that a transitive dependency requests.
Concrete example
websocketpp@0.8.2(patched, noboost.asiodep)websocketpp@0.8.2.bcr.5(unpatched, depends onboost.asio@1.89.0)websocketpp >= 0.8.2websocketpp@0.8.2is used0.8.2.bcr.5from BCR (higher version in the merged pool), ignoring the internal registry entirely. The patch is silently lost.This was independently confirmed by @guw in #25722:
Observed behavior vs documented behavior
The
--registryflag documentation states:In practice, this is not enforced per-module. Versions from all registries are merged into a single pool before MVS runs. A module present in the first registry can still be resolved from a later registry if a higher version exists there.
Why this matters
Organizations maintaining internal registries with patched dependencies have no reliable way to ensure their patches are used. The only workaround today is forking the entire BCR and using a single
--registryflag, which is a significant maintenance burden.Related: #25722 - How to patch dependencies of non-root module in bzlmod
Proposal:
--registry-exclusiveflagAdd a new flag
--registry-exclusive=<registry_url>that marks a registry as authoritative for any module it contains.Behavior
When a registry is marked exclusive:
metadata.jsonfor the moduleUsage
With the example above:
websocketppexists in the internal registry -> only0.8.2is available -> MVS picks0.8.2-> patch is appliedrules_pkgdoes not exist in the internal registry -> falls through to BCR -> resolved normallyWhy this design
--registry-exclusiveis opt-inMODULE.bazelsyntax or registry formatImpact
This would unblock organizations that:
The current workarounds (forking BCR, version bumping tricks, adding transitive deps as direct deps) are all brittle and high-maintenance.
Which category does this issue belong to?
Core
What underlying problem are you trying to solve with this feature?
No response
Which operating system are you running Bazel on?
Linux 22.04
What is the output of
bazel info release?release 7.4.0
If
bazel info releasereturnsdevelopment versionor(@non-git), tell us how you built Bazel.No response
What's the output of
git remote get-url origin; git rev-parse HEAD?Have you found anything relevant by searching the web?
No response
Any other information, logs, or outputs that you want to share?
No response