Skip to content

Feature request: --registry-exclusive flag for per-module registry ownership in Bzlmod #29248

Description

@BalasubramanyamEvani

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:

  1. During module resolution, Bazel checks if the exclusive registry has a metadata.json for the module
  2. If yes, only versions from that registry are considered by MVS. All other registries are skipped for that module.
  3. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3We're not considering working on this, but happy to review a PR. (No assignee)awaiting-user-responseAwaiting a response from the authorteam-ExternalDepsExternal dependency handling, remote repositiories, WORKSPACE file.type: feature request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions