3.2.5 Flag Lifecycle

Bazel's command line has hundreds of flags, and the surface keeps changing across releases.1 Many of those flags carry a prefix — --experimental_* or --incompatible_* — that encodes the feature's maturity stage. Understanding what those prefixes mean is a practical skill: it tells you whether a flag is safe for your .bazelrc, whether a Stack Overflow answer still applies to your Bazel version, and when a workflow you depend on is about to change.

Flag lifecycle stages
Prefix tells you maturity — experimental through removal
EXPERIMENTAL
Under development
No stability guarantees
--experimental_*
INCOMPATIBLE
Breaking change announced
Migration recipe available
--incompatible_*
DEFAULT
New behavior is on
Flag stays as escape hatch
--noincompatible_*
REMOVED
Old behavior gone
Migration mandatory
flag deleted
Each major release flips incompatible flags to default and may remove flags that reached end of life.

The four stages

Bazel flags follow a predictable progression through four stages2,3:

--experimental_* flags gate features under active development. The Bazel team makes no stability guarantees for them — semantics can change or the flag can be removed without notice between releases2. That means they are fine to explore locally, but risky in a shared .bazelrc that CI and the whole team rely on. APIs and behaviors behind an --experimental_* flag are explicitly excluded from Bazel's backward-compatibility promise2.

--incompatible_* flags signal a breaking change that is coming. When the Bazel team commits to a behavior change, the new behavior is gated behind an --incompatible_* flag that defaults to off2,3. This gives the ecosystem a migration window: you can opt in early, validate your build, and adapt your code before the flag flips. Each incompatible flag has a corresponding GitHub issue explaining what changes, why, and how to migrate2.

Default behavior. In a subsequent major release, the --incompatible_* flag flips to on — the new behavior becomes the default. The flag itself may remain temporarily as an opt-out escape hatch, letting teams that haven't migrated yet revert while they catch up2.

Flag removed. Eventually the flag is removed entirely and the old behavior is gone. At this point the migration is mandatory.

The migration contract

Bazel's backward-compatibility policy establishes a contract between the Bazel team and its users2,3:

  1. Every incompatible change gets a GitHub issue labeled incompatible-change, with a description and a migration recipe2.
  2. The incompatible flag is recommended to be back-ported to the latest LTS release without enabling it by default, so users can migrate before the next LTS arrives2.
  3. When the flag is ready for migration at HEAD, the issue gets a migration-ready label. The issue is closed when the flag flips2.

The intended workflow for users is: update Bazel, apply the migration recipes, validate with the incompatible flags turned on, then run production builds with Bazel as released — no --experimental_* or --incompatible_* flags required3.

Why flags accumulate

In practice, the lifecycle does not always complete on schedule. The Bazel team is small relative to the flag surface area, and many flags have not finished the full progression from experimental to removal.1 Historical snapshots have shown steady flag-count growth rather than cleanup, so treat the exact count as a moving target and focus on the prefix and default in the Bazel version you actually run.1

This is why community efforts like the Rules Authors SIG bug bounties exist — offering $1,000 rewards for completing the work needed to flip an incompatible flag default1. Flipping a default is not a one-line change in the Bazel repository. It requires fixing affected downstream rulesets and projects first, then coordinating the default change with Google's internal Blaze codebase1.

Reading flags in practice

When you encounter a flag in documentation, a blog post, or an error message, the prefix tells you how to interpret it:

  • An --experimental_* flag in a tutorial may not exist in your Bazel version, or may behave differently than described. Check bazel help to verify the flag exists and read the current description.
  • An --incompatible_* flag set to true in a .bazelrc recommendation may already be the default in your version. Setting it explicitly is harmless but unnecessary once the flag has flipped.
  • A flag without either prefix is stable API. Changes to its semantics follow semantic versioning — breaking changes only in major releases2.

A good starting point for recommended incompatible flags is a curated .bazelrc flags guide4, which lists flags worth enabling early. Many of those flags started as --experimental_*, graduated to --incompatible_*, and are now defaults in later Bazel versions. Such guides usually explain which version ranges each flag applies to.

The version-aware bazelrc-preset.bzl tool takes this further: its checked-in FLAGS table applies explicit Bazel-version predicates to curated recommendations, while MIGRATIONS holds upcoming-major flips included by the opt-in strict preset.1,5 It does not infer that an arbitrary default is wrong. It renders maintained policy data for the detected Bazel version into a file the repository vendors and reviews.

Preparing for the next major version

When planning a Bazel major version upgrade, the flag lifecycle becomes directly actionable. Bazelisk provides two flags for this6,1:

  • bazelisk --strict expands to the set of --incompatible_* flags that may be enabled for the current Bazel version, giving you a preview of upcoming breakage6.
  • bazelisk --migrate enables one incompatible flag at a time and prints a report showing which flags pass and which need migration work.

The practical recommendation is to opt in to upcoming incompatible flags early rather than waiting for the upgrade.1 For a team staying on an older LTS, testing the flags planned for the next active LTS prevents issues from accumulating over a year of active development. M4 Bazel Version Upgrades develops the full workflow: compatibility matrices, release-note triage, canarying, and rollback planning.

key takeaway

The --experimental_* prefix means "not ready for production." The --incompatible_* prefix means "this will become the default — migrate now." Everything without a prefix is stable API. When a flag appears in documentation or a recommendation, check whether it still exists in your Bazel version and whether its default has already changed.

Check your understanding · 3 questions

1.Match each flag prefix to its stability guarantee:

Drag each answer onto the matching prompt, or click an answer and then click a prompt

Answers
--experimental_*
--incompatible_*
No prefix (stable)
Removed flag

2.True or false about the flag lifecycle migration contract:

Choose True or False for each sentence

Setting an --incompatible_* flag to true in .bazelrc is harmless even after it has already become the default in your Bazel version.
An --incompatible_* flag that has just flipped to enabled by default may temporarily remain negatable as an escape hatch.
Production builds should use --experimental_* flags to access the latest Bazel features.

3.What is the purpose of 'bazelisk --migrate'?

Select one answer

0 of 3 answered

Footnotes

  1. Better Bazel Flag Defaults — flag count growth across versions, Rules Authors SIG bounties, and the bazelrc-preset.bzl launch 1 2 3 4 5 6 7 8

  2. Backward Compatibility — official policy for --incompatible_* flags, migration recipes, and the migration-ready label process 1 2 3 4 5 6 7 8 9 10 11

  3. BazelCon 2018 Day 1: Bazel Updates and Roadmap — migration window + flag + recipe workflow, and the rule that production builds should not require incompatible flags 1 2 3 4

  4. .bazelrc flags you should enable — recommended incompatible flags for correctness and hermeticity

  5. bazelrc-preset.bzl repository mapflags.bzl separates version-scoped FLAGS from strict-mode MIGRATIONS, while bazelrc-preset.bzl renders and drift-checks the vendored preset.

  6. Bazelisk — A user-friendly launcher for Bazel--strict and --migrate flags for previewing incompatible changes 1 2