2.3.4 First-Response Debugging Flags

2.3.2 Sandboxing explained why undeclared inputs fail under isolation. When that failure actually happens, the first job is simpler: see the failed command, see the sandboxed world around it, and decide whether you are debugging a missing input or something else. Bazel's first-response trio for that is --verbose_failures, --sandbox_debug, and --subcommands.1,2

Three flags, three questions

--verbose_failures answers "what exactly failed?" It prints the full command line for failed commands only.2 This is the smallest useful rerun when the build already failed and you need the concrete command line. Treat it as diagnostic evidence rather than a guaranteed standalone reproduction: the original working directory, environment, param files, and sandbox may still matter.

--subcommands answers "what is Bazel running overall?" It prints the full command line for every command before execution, not just the ones that fail.2,3 That makes it much noisier, but it is the right tool when the failing command depends on wrapper scripts, generated intermediates, or earlier successful actions that matter to the story. If the command lines are hard to read, --subcommands=pretty_print prints the arguments as a list instead of one long shell line.2,4

--sandbox_debug answers "what files did the action really see?" It prints extra sandbox debugging information and preserves the sandbox directories so you can inspect the staged inputs after the failure.1,2 Because those directories are left behind, turn the flag back off when you are done debugging so it does not keep filling disk space.1

A simple escalation path

When a sandboxed action fails, start with the least noisy rerun:

bazel build //pkg:target --verbose_failures

If the error smells like "file not found", wrong relative path, missing tool, or some host-only assumption, rerun with sandbox inspection enabled:

bazel build //pkg:target --verbose_failures --sandbox_debug

Now you can inspect the preserved sandbox instead of guessing which files Bazel staged for the action.1,2,4 This is the practical version of the rule from 2.3.2 Sandboxing: only declared inputs are there.

Reach for --subcommands=pretty_print when the final error still is not enough, or when you need to see the exact sequence of executed commands around it:

bazel build //pkg:target --subcommands=pretty_print

That complements 2.2.3 Static Action Graph: use the flag when you need the execution stream, and go back to the action-graph view when you need the planned structure behind it.2,4

Diagnosis is not the permanent configuration

A temporary comparison with --spawn_strategy=local can confirm that sandboxing is what exposed the problem.3 If that makes the failure disappear, the most common lesson is not "leave this action local forever." The usual lesson is that the action depended on an undeclared file, host path, or tool that the sandbox stopped accidentally providing.1,3

The durable repair is to model that input correctly. Strategy choices themselves are the next topic in 2.5.2 Strategies & Mnemonics, and the place to encode persistent project defaults or .bazelrc overrides is 3.2.6 Hermeticity Settings.2 These flags also solve a different problem than 2.4.4 Diagnosing Cache Misses: use the trio here when an action fails or behaves strangely at execution time, and use --explain later when the build succeeds but Bazel rebuilt more than you expected.2

think

Decide: The build fails in the sandbox but passes with --spawn_strategy=local. A teammate proposes adding build --spawn_strategy=local to .bazelrc so CI goes green. Is that a fix?

Reveal

Usually no. That comparison is evidence, not the repair. Passing under local means the normal execroot happened to provide something the sandbox withheld: an undeclared file, a host path, a tool on PATH, or another ambient dependency.

Use --verbose_failures to capture the failing command and --sandbox_debug to inspect the staged sandbox. The durable fix is to model the missing input or tool, not to make CI less hermetic and hope the next machine has the same accident.

key takeaway

Use the flags as escalating lenses: --verbose_failures for the failed command, --sandbox_debug for the staged world around it, and --subcommands when you need the full execution stream. If changing strategy makes the error disappear, treat that as evidence about the bug and then follow the fix into 2.5.2 Strategies & Mnemonics and 3.2.6 Hermeticity Settings, not as a permanent workaround.1,2,3

Check your understanding · 3 questions

1.Match each debugging flag to the question it answers:

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

Answers
--verbose_failures
--sandbox_debug
--subcommands

2.When should you reach for --subcommands=pretty_print instead of --verbose_failures?

Select one answer

3.True or false: correct use of the debugging trio.

Choose True or False for each sentence

If switching to --spawn_strategy=local makes a failure disappear, the usual lesson is that the action depended on an undeclared file or host path the sandbox was blocking.
--sandbox_debug should be left permanently in .bazelrc for ongoing builds because it has minimal performance impact.
0 of 3 answered

Footnotes

  1. Sandboxing — first-response rerun advice, preserved sandbox directories, and why sandbox failures usually expose undeclared inputs 1 2 3 4 5 6

  2. Command-Line Reference — canonical semantics of --verbose_failures, --subcommands, --subcommands=pretty_print, and --sandbox_debug 1 2 3 4 5 6 7 8 9 10

  3. Bazel flag cheat sheet — debugging-flag quick reference and the temporary --spawn_strategy=local comparison when sandboxing is suspected 1 2 3 4

  4. Sponsored Session: Enough Bazel to Be Dangerous: A Debugging Cookbook - Instructor: Alejandro Gomez — practical use of --sandbox_debug for wrong-path debugging and --subcommands=pretty_print for inspecting executed commands 1 2 3