Bazel Versions Break SemVer Parsers: protobuf, .bcr.N, and MVS
Ecosyste.ms's latest-version query on Bazel Central Registry mislabels protobuf as 3.19.6 and glog as 0.7.1 because it applies strict SemVer parsing to Bazel's intentionally loosened version format. The fix ports Version.java's ordering as an implementation-defined vers:bazel scheme, allowing .bcr.N patch suffixes to be treated as stable releases that sort above their bases.
Packages.ecosyste.ms currently reports protobuf's latest Bazel Central Registry release as 3.19.6 even though 36.0.bcr.1 exists. The discrepancy is not a stale cache but a systematic filter: the latest-release query rejects every version that the strict SemVer parser cannot parse, and Bazel's version grammar intentionally allows release segments beyond SemVer's three-integer shape.1
Context
Bazel's bzlmod accepts standard SemVer and extends it: release segments may be arbitrary dot-separated ASCII alphanumeric identifiers, so 36.0 and 20210324.2 are valid. Version.java orders these segments with SemVer-style precedence rules, and Minimal Version Selection uses that ordering, making the .bcr.1 patch suffix a deliberate way to sort above a yanked base release.1
Assessment
The constraint that moved is that ecosystem-version tooling cannot assume SemVer's exact grammar. The proposed fix gives Bazel a dedicated vers:bazel comparison scheme by porting Version.java's sort, avoids both the strict rejection and the permissive prerelease misordering of .bcr.N suffixes, and lets the yanked flag exclude 36.0 while keeping 36.0.bcr.1 stable.1
Caveats
The post is a first-party account by the maintainer of the vers libraries, and the ecosyste.ms change is asserted rather than independently verified. I would change my assessment if the fix is not deployed, if Bazel revises Version.java's ordering, or if 36.0.bcr.1 turns out not to sort above 36.0 in Bazel's actual resolver.1
Aman Sharma reported this week that packages.ecosyste.ms had the latest version of protobuf on the Bazel Central Registry (BCR, the default package index for Bazel’s built-in dependency manager) as 3.19.6 . The current release is 36.0.bcr.1 . Protobuf dropped the leading 3. in 2022 and has shipped 21.x through 36.x since, so 3.19.6 is four years behind.
The root cause is that the “latest release” query filters out anything the semantic gem fails to parse. That code predates the vers library and I’ve yet to swap it over, so it’s still applying one strict SemVer parser to every ecosystem. SemVer requires exactly three numeric segments before the hyphen. 36.0 has two, 0.7.1.bcr.1 has five, and bcr contains letters, so every protobuf release since 3.19.6 fails the parse and gets treated as unstable. The same filter reports glog’s latest as 0.7.1 when the registry has 0.7.1.bcr.1 .
Bazel is a build tool first, and its module system (bzlmod, the default since Bazel 7) is mostly used to pull in existing C++, Java and Go projects that already have their own release histories. Rather than force those projects to renumber, Bazel documents its version format as a deliberate loosening of SemVer: any SemVer string is valid and sorts the same way, and beyond that the release part can have any number of segments and each segment can contain letters. Abseil’s date-based 20210324.2 and protobuf’s two-segment 36.0 are both accepted as-is. The grammar in Version.java is RELEASE[-PRERELEASE][+BUILD] , where RELEASE is dot-separated identifiers of ASCII letters and digits only. Comparison applies SemVer’s prerelease identifier rules to the release segments as well:
numeric identifiers sort numerically, so 36.0 > 4.0.0
any identifier containing a letter sorts above every purely numeric one, then as an ASCII string
a version with more release segments sorts above one that shares its prefix, so 0.7.1.bcr.1 > 0.7.1
a prerelease sorts below the same release part on its own, so 36.0-rc2 < 36.0
The .bcr.N suffix is a registry convention rather than part of the format. Registry entries are immutable once published, and the registry often carries small patches on top of the upstream tarball to make a project build cleanly under Bazel. When one of those patches needs updating and the upstream source stays as-is, the fixed entry is published under a new version with .bcr.1 tacked onto the release part. That’s why 36.0.bcr.1 exists: 36.0 was yanked for a macOS toolchain integrity mismatch, and the fix went in as extra release segments rather than a +build tag so that it sorts strictly above 36.0 . That ordering matters because Bazel resolves dependencies with Minimal Version Selection, the same algorithm as Go modules, which always picks the highest version any dependent asked for; a suffix that sorted equal or lower would leave everyone pinned to the broken build.
Version.java also handles build metadata differently from SemVer §10: rather than being ignored for comparison and kept, the +BUILD part is stripped at parse time before anything is stored or sent to a registry, so 1.2.3+abc and 1.2.3 are equal and interchangeable rather than equal-but-distinct. The empty string is a valid version and sorts higher than everything else; it marks a module that’s been overridden to point at a local directory or git commit instead of a registry release, and sorting it top means a local override always wins MVS. Since the release segments have no fixed meaning, breaking changes were originally signalled by a separate compatibility_level integer in the module file, roughly equivalent to a SemVer major version. Both active release lines made that field a no-op in February 2026 (9.1.0, backported to 8.6.0) after it caused resolution failures only the module authors could resolve, leaving breaking changes to be reported by the module’s own build-time errors instead.
External parsers get .bcr.N wrong one of two ways:
strict SemVer (the semantic gem): rejects 0.7.1.bcr.1 because the release part must be exactly three integers, so it drops from the stable set
permissive generic (the vers fallback): accepts the extra segments, applies prerelease semantics to the alphabetic bcr , and sorts it below 0.7.1
The vers spec, the package-url project’s cross-ecosystem notation for version ranges, enumerates comparison schemes for fifteen ecosystems and has yet to cover Bazel, though pkg:bazel is a registered purl type so there is already a slot for a vers:bazel scheme. Both vers libraries I maintain hit the second case. The fix in Ruby and Go is a port of the Version.java sort as an implementation-defined bazel scheme ahead of the spec. The corresponding ecosyste.ms change keeps .bcr.N releases eligible as stable, lets the yanked flag exclude 36.0 , and moves both protobuf and glog to the correct latest version.
The source text above remains the property of its publisher and is reproduced for commentary.