5.6.2 Bisecting Bazel Version Issues

When a build works with one Bazel version and fails with another, comparing the two releases is only the start of the diagnosis. Bazelisk can test the commits between them and identify the first Bazel commit whose result changes. That turns “the upgrade broke us” into a small, reviewable change that can support a useful upstream bug report.1

This workflow builds on the version-selection role of 0.1.1 Bazelisk & .bazelversion.

First prove that the boundary is real

Choose the smallest command that still reproduces the problem. A focused query, build, or test usually makes each bisect step faster and reduces unrelated failures. Run it more than once at both endpoints under the same flags, environment, platform, and external services. A flaky test, expiring credential, or changing remote service does not become deterministic merely because a bisector invokes it.

Suppose this command succeeds with Bazel 8.0.1 and fails with Bazel 8.1.0:

bazelisk --bisect=8.0.1..8.1.0 test --config=ci //payments:contract_test

The range is ordered <GOOD>..<BAD>. Bazelisk resolves the endpoints, obtains the intervening commits through GitHub's compare API, and repeatedly runs the command after the range. The workspace's .bazelversion does not select the versions tested by this operation. If the endpoints do not have the expected results, stop and repair the reproduction instead of interpreting the midpoint tests.2,3

The endpoints may be releases, supported release-candidate forms, or commit hashes from Bazel's main or release- branches. For example, the official Bazel release documentation uses this release-candidate range:3

bazelisk --bisect=6.1.0..release-6.2.0rc2 build //foo:bar

For a regression that was later fixed, reverse the meaning of success and failure by putting ~ before the whole range:

bazelisk --bisect=~6.0.0..HEAD test //payments:contract_test

This asks for the first tested commit where the command starts succeeding. The tilde is part of the --bisect value. It is not a prefix on either endpoint.2

Control state only when the reproduction requires it

Bazelisk normally lets successive tests reuse Bazel state. That is desirable when the real failure occurs in an ordinary warm workflow. If server state is part of the suspected cause, BAZELISK_SHUTDOWN=1 runs bazel shutdown between tests. If the reproduction specifically requires an empty output base, BAZELISK_CLEAN=1 runs bazel clean --expunge between tests.2

BAZELISK_CLEAN=1 bazelisk --bisect=8.0.1..8.1.0 \
  query --config=ci @rules_cc//:all

The stronger reset is expensive: it discards incremental evidence, shuts down the server, and can force repository fetching again. Use it only after showing that the clean state is necessary to reproduce the same symptom. The case study behind this command used it for a failure during external dependency download, then reduced the original full test suite to a query that still triggered that download.1

On shared networks, BAZELISK_GITHUB_TOKEN supplies a token for Bazelisk's API requests and can avoid unauthenticated GitHub rate limits. It does not affect the behavior of the Bazel command being classified.2

Understand what Bazelisk can actually test

Bazelisk uses prebuilt Bazel binaries for bisect commits on Bazel's main and release branches. Commit-hash binaries exist only for commits that passed Bazel CI, so a requested midpoint can be unavailable for a particular platform. The automatic bisector cannot bisect commits that exist only in a local Bazel checkout.2

This distinction matters when a download returns 404. That outcome says “this candidate could not be tested,” not “the regression reproduces here.” Do not let an unavailable binary become the bad result. Bazelisk's automatic bisector has no skip control, so switch to git bisect when gaps prevent it from narrowing the range.1

The special version selectors are useful for establishing preview endpoints, but they are not interchangeable:2

SelectorResolves to
last_greenThe most recent Bazel commit that passed Bazel CI
last_rcThe most recent release candidate, or the latest release when no candidate is active
rollingThe latest rolling release, even if a newer LTS release exists

These selectors apply only to official Bazel releases, not forks. Because they move over time, record the resolved build label or commit with the investigation rather than reporting only “last_green failed.”

USE_BAZEL_VERSION can select an exact release or CI-built commit for one ordinary Bazelisk invocation. A GitHub-hosted fork instead uses <FORK>/<VERSION> and release assets named according to Bazel's conventions. for example, USE_BAZEL_VERSION=my-org/8.0.0 selects version 8.0.0 from the my-org/bazel fork. BAZELISK_BASE_URL changes the base download server and Bazelisk appends /<VERSION>/<FILENAME>. Those mechanisms select already published binaries. They do not make --bisect traverse a fork or local patch series.2

Fall back to git bisect for skip control

Clone the Bazel repository separately from the project that reproduces the failure. The following starts a first-parent bisect without changing the clone's worktree. Git records the candidate commit in .git/BISECT_HEAD:1

git bisect start --no-checkout --first-parent 8.1.0 8.0.1

For each candidate, read that hash, run the reproducer in the project through Bazelisk, and classify the result:

USE_BAZEL_VERSION="$(git rev-parse BISECT_HEAD)" \
  bazelisk test --config=ci //payments:contract_test

Mark a reproducing failure git bisect bad, a success git bisect good, and an unavailable or otherwise untestable candidate git bisect skip. An automated git bisect run script communicates that last state by exiting 125. Ordinary zero and nonzero results retain their good and bad meanings. The script must recognize infrastructure and download failures separately from the regression, or it will confidently bisect the wrong predicate.1

--no-checkout is appropriate here because Bazelisk downloads an existing CI-built binary for the hash. It is not a source-build fallback. If too many candidate binaries are absent, this method may produce only a set of possible culprits. Building arbitrary Bazel commits from source requires checking out the candidate and satisfying that revision's bootstrap requirements. That is a different, version-dependent procedure and should not be hidden behind the prebuilt-binary workflow.

When the bisect finishes, re-run the boundary commits, inspect the identified change, and capture the platform, exact command, logs, and minimal reproduction. A bisect identifies where an observable result changed. It does not by itself prove why the change caused the symptom.

key takeaway

Use bazelisk --bisect=<GOOD>..<BAD> only after establishing a deterministic endpoint boundary with a focused command. Use ~<GOOD>..<BAD> to find a fix, and reset state with BAZELISK_SHUTDOWN or BAZELISK_CLEAN only when that state is part of the reproduction.

Bazelisk bisects official main or release history through available prebuilt binaries. When a candidate cannot be tested, switch to git bisect for its skip semantics, keep download failures separate from the regression, and report the result as a narrowed change—not yet a causal explanation.

Check your understanding · 4 questions

1.A focused test passes with Bazel 8.0.1 and fails with 8.1.0. Which invocation asks Bazelisk to find the first regression commit?

Select one answer

2.A regression exists in Bazel 6.0.0 but a later commit fixes it. Which range asks Bazelisk to locate the first tested commit where the command succeeds?

Select one answer

3.A bisect reproducer normally runs in a warm workspace, but an engineer proposes BAZELISK_CLEAN=1 for every candidate. What is the best policy?

Select one answer

4.Bazelisk receives a 404 for a candidate's prebuilt Bazel binary. Which responses preserve a sound bisect?

Select all that apply

0 of 4 answered

Footnotes

  1. Troubleshooting Bazel with Git Bisect — focused reproduction, clean-state example, missing-binary failure, and manual git bisect skip workflow 1 2 3 4 5

  2. Bazelisk — A user-friendly launcher for Bazel — authoritative bisect syntax, reverse mode, supported binaries and selectors, environment variables, forks, and custom download bases 1 2 3 4 5 6 7

  3. Release Model — official regression-reporting workflow and release-candidate bisect example 1 2