3.2.6 Hermeticity Settings
The .bazelrc hierarchy from 3.2.1 .bazelrc Hierarchy determines where flags live and who can override them. This item is about which flags to put there. A handful of settings close the most common paths through which the host environment leaks into build actions, breaking the cache sharing and reproducibility guarantees that 2.3 Hermeticity & Sandboxing described conceptually.
Strict action environment
The single highest-impact hermeticity flag is --incompatible_strict_action_env. Without it, Bazel forwards environment variables like PATH from the invoking shell into every action's command environment. Two developers with different PATH values produce actions with different cache keys even when building identical source, which causes unexplained cache misses across machines and between local builds and CI.1,2
With the flag enabled, Bazel stops forwarding the client shell and instead provides a minimal, fixed baseline environment to actions. Beyond that baseline, only variables explicitly added via --action_env reach the action command line.3
build --incompatible_strict_action_env
This flag followed the lifecycle pattern described in 3.2.5 Flag Lifecycle: it started as --experimental_strict_action_env, was renamed to --incompatible_strict_action_env, and later became a default behavior.1 Older Bazel lines may still need the flag set explicitly, while newer lines may keep it only as an opt-out escape hatch. Check bazel help build --long or the command-line reference for the default in the Bazel version you run.
Sandbox network access
By default, sandboxed actions are allowed to reach the network. That means an action can silently fetch data from a remote service without declaring a dependency on it, and the build will succeed until the service is unavailable or the network is blocked.1,4
build --sandbox_default_allow_network=false
Setting --sandbox_default_allow_network=false blocks network access for all sandboxed actions. Actions that legitimately need the network can opt out with tags = ["requires-network"] on the target.1 This surfaces undeclared network dependencies early instead of letting them hide until a CI runner or air-gapped environment reveals them.
The network restriction applies only to sandboxed execution. Actions running with the local strategy or on platforms without sandbox support (notably Windows) are unaffected.3
Controlling individual environment variables
Sometimes an action genuinely needs a host variable, for example a cross-compiler path or a cloud credential token. --action_env provides controlled forwarding with two forms:5,3
build --action_env=CC=/usr/bin/clang-18
build --action_env=CLOUD_TOKEN
Compare: These two .bazelrc lines differ by one missing =value:
build --action_env=CC=/usr/bin/clang-18
build --action_env=CC
One keeps a remote cache shareable across machines. The other quietly breaks it. Which is which?
Reveal
--action_env=CC=/usr/bin/clang-18 pins a value. Every machine injects the same CC, so identical sources can still compute matching action keys. --action_env=CC inherits CC from each developer's shell. A gcc laptop and a clang CI worker now describe different actions for the same source.
The trap is that both lines look like "allow CC into the action." They are opposite cache contracts: one standardizes the environment, the other makes the caller's environment part of the key. The print_env example below is the cheap way to make that invisible split visible.
That is the pin-versus-inherit split. The first form is safe for cache sharing because every machine sees the same value. The second form reintroduces the same per-machine divergence that --incompatible_strict_action_env was designed to prevent. Use the inheriting form only when the variable genuinely varies per machine and you accept the cache impact.3
The hermeticity-settings print_env genrule makes that difference observable: its cmd writes ${DEMO_VALUE:-unset}, so the same target produces unset without --action_env=DEMO_VALUE=fixed and fixed with it.
When an action requires a variable but the build never declares it, strict action environments turn the hidden host assumption into a normal build failure:
genrule(
name = "needs_env",
outs = ["token.txt"],
cmd = "test \"$${DEMO_TOKEN:-}\" = expected || exit 1",
)
missing DEMO_TOKEN in action environment
fix: bazel build --action_env=DEMO_TOKEN=expected //env:needs_env
Read this as an execution-phase modeling bug: the action can build only if a host variable is present, but the target or invocation did not make that dependency explicit. The controlled fix is to pass a pinned value:
bazel build --action_env=DEMO_TOKEN=expected //env:needs_env
--host_action_env is the equivalent for actions that run in the host configuration -- build tools and code generators that are built for the machine running the build, such as when cross-compiling.4
Hermetic toolchains
Flags alone cannot make a build hermetic if the compiler itself comes from /usr/bin/gcc. Different machines ship different compiler versions, link against different system libraries, and embed different paths into binaries, all invisible to Bazel's action cache key.3,4
The fix is hermetic toolchains: download a specific compiler version as part of the build rather than relying on whatever the host has installed. Most modern rulesets provide this by default. For example, rules_java auto-downloads a JDK via remote_java_repository, and rules_go downloads a pinned Go SDK. The C/C++ built-in rules are the notable exception: they default to auto-detecting the host compiler, making them a common hermeticity gap.3
Toolchain authoring and registration are covered in 4.6 Toolchains & Platform Resolution. From a maintainer's perspective, the action item is simpler: check that every language ruleset in your MODULE.bazel uses a downloaded toolchain, not the system one.
A typical hermeticity block
Combining the flags into a shared workspace .bazelrc gives every developer and CI job the same hermeticity baseline:
build --incompatible_strict_action_env
build --sandbox_default_allow_network=false
On newer Bazel lines the first line may be redundant but harmless. On older lines it is essential. The hermeticity-settings .bazelrc groups the env-pinning and sandbox-network lines under a build:strict named config so the same flags can be exercised on demand. The maintainer-workspace .bazelrc keeps a similar set on the unconditional build block — --spawn_strategy=sandboxed, pinned --java_runtime_version=remotejdk_11, and --sandbox_default_allow_network=false — so every command inherits the hermeticity baseline rather than relying on a named config. If actions still behave unexpectedly after setting these flags, execution-log comparison from 5.7 Cache & Execution is the next diagnostic step.
Three settings close the most common hermeticity gaps: strict action env stops the host shell environment from leaking into actions, --sandbox_default_allow_network=false blocks undeclared network access, and hermetic toolchains remove the dependency on whatever compilers the host has installed. Put the non-default pieces in the shared workspace .bazelrc so every build inherits the same baseline.
Diagnosing leaks after the flags are set
Even with these flags in place, non-determinism can still leak through timestamps, sort ordering, or tools that read system state. The execution log is the definitive tool for finding these leaks: run two clean builds, capture --execution_log_compact_file from each, and diff the results. Differences in action output hashes reveal exactly which action is non-deterministic and which inputs diverged.4,6
That workflow is covered in depth in 5.7 Cache & Execution. At the maintainer level, the important takeaway is that --incompatible_strict_action_env and --sandbox_default_allow_network=false eliminate the most frequent leak categories, but they are not a complete guarantee. Periodic execution-log comparison in CI is the way to catch what the flags miss.
Check your understanding · 3 questions
1.Two developers have different PATH values on their machines. Without any hermeticity flags, how does this affect Bazel's action cache?
Select one answer
2.Which settings close the most common hermeticity gaps for a shared Bazel workspace? Select all that apply.
Select all that apply
3.True or false about hermeticity settings:
Choose True or False for each sentence
Footnotes
-
.bazelrc flags you should enable — recommends
--incompatible_strict_action_envand--sandbox_default_allow_network=falseas essential hermeticity flags ↩1 ↩2 ↩3 ↩4 -
Presets for bazelrc — curated preset files that bundle correctness flags including strict action env ↩
-
How to keep a Bazel project hermetic? — comprehensive guide covering
--incompatible_strict_action_env,--action_env, execution strategies, and toolchain hermeticity ↩1 ↩2 ↩3 ↩4 ↩5 ↩6 -
Bazel and action (non-) determinism — practical tips including
--nosandbox_default_allow_network,--strict_action_env,--action_env, and hermetic toolchains ↩1 ↩2 ↩3 ↩4 -
Commands and Options —
--action_envsyntax and semantics ↩ -
Hermeticity — canonical definition and troubleshooting strategies for hermetic builds ↩