Diagnosing Bazel Sandbox Execution Failures on Linux and macOS
A diagnostic workflow for Bazel 'Sandboxed execution failed' errors on Linux and macOS: match the error text to a cause, inspect declared inputs with aquery, apply targeted fixes, and know when to escalate.
28 Sept 2025, 20:17 UTC

The recognizable condition
Your build fails with Sandboxed execution failed or Process exited with status 1, and the error mentions linux-sandbox or sandbox-exec. The decisive symptom: the same target builds fine with --spawn_strategy=local but fails with the default sandboxed strategy. That split tells you the action itself works — it just does something the sandbox forbids, usually reading or writing a file that was never declared as an input.
This guide assumes Bazel 7.x or 8.x, where sandboxing is on by default for local execution on Linux. It covers local sandboxing only; remote execution uses a different model.
Match the error to a cause
| Error text inside the sandbox | Likely cause |
|---|---|
No such file or directory for a project file | Missing input: the file is not in the rule's srcs/deps |
command not found or dynamic linker errors | Undeclared tool or runtime dependency |
Operation not permitted on readlink | Symlink resolving outside the sandboxed execroot |
Permission denied writing to /tmp or $HOME | Action writes outside its declared output directories |
clone() failed / unshare() failed | Nested sandbox: running inside Docker or a CI container without user-namespace support |
Operation not permitted from sandbox-exec (macOS) | System Integrity Protection or missing Full Disk Access for the terminal |
Ordered checks
Work through these in order; each narrows the search space.
- Reproduce with debug output. Run
bazel build //failing:target --verbose_failures --sandbox_debugfrom your workspace root (normal user permissions suffice).--sandbox_debugkeeps the sandbox directory after the action finishes so you can inspect what the action actually saw. Note: these directories consume disk space — delete them under the sandbox base after diagnosis. - Capture the exact command. Re-run with
--spawn_strategy=local --subcommands. This prints the full argv the action executes. If the command references a path that exists in your workspace but the sandboxed run can't see it, you have a missing-input problem. - Compare declared inputs against reality. Run
bazel aquery "mnemonic('Genrule', //failing:target)"(adjust the mnemonic) and inspect the action's input list. Any file the command touches that is absent from that list is a hermeticity violation the sandbox is correctly catching. - Inspect the sandbox layout. With
--sandbox_debugactive,cdinto the preserved sandbox directory and check whether the expected input files are symlinked in. Missing symlinks confirm the input was never declared. - Rule out the sandbox binary itself. On Linux, verify the sandbox helper exists and is executable:
ls -la $(bazel info output_base)/../../bazel-*/linux-sandbox. On macOS,which sandbox-execshould return/usr/bin/sandbox-exec. - Rule out filesystem restrictions. If your build runs on a filesystem mounted
nosuidor under quota, retry with--sandbox_base=/tmp/bazel-sandbox(or another suitable local path) to move sandbox staging off the problem mount.
Fixes tied to each finding
Missing inputs. Add the file to the rule's srcs, or if the file is generated, produce it with ctx.actions.declare_file in a custom rule and wire it through deps. For a genrule that reads a data file, the fix is typically:
genrule(
name = "render",
srcs = ["template.html", "data.json"], # data.json was previously undeclared
outs = ["out.html"],
cmd = "./render.sh $(location template.html) $(location data.json) > $@",
tools = ["render.sh"],
)Undeclared tools. Move the tool from an implicit system dependency into tools (genrule), exec_tools (custom rules), or a proper toolchain. Relying on a binary from the host PATH is exactly what the sandbox blocks — and it would break on a clean machine anyway.
Symlink escapes. Restructure so symlinks resolve within the execroot. Symlinks crossing package or repository boundaries are fragile under sandboxing; prefer declaring the target file directly.
Writes to /tmp or $HOME. Redirect the tool's scratch/output paths to the action's declared outputs or a directory under $TEST_TMPDIR (tests) or the genrule output tree. Many tools accept a flag or environment variable (e.g., TMPDIR) to relocate temp writes.
Nested containers (CI/Docker). The Linux sandbox uses user namespaces, which many container runtimes block by default. Options: run the container with the privileges needed for unshare, or fall back to --spawn_strategy=local in that CI job. Treat the fallback as a workaround — it hides hermeticity violations, so keep at least one sandboxed CI lane if possible.
macOS permissions. Grant your terminal emulator Full Disk Access in System Settings, then retry. macOS sandboxing uses sandbox-exec (Seatbelt), a completely different mechanism from Linux namespaces, so Linux-specific fixes do not transfer.
Verify the fix
Confirm the sandbox now passes and is actually exercising the action:
bazel clean --expunge
bazel build //failing:target --spawn_strategy=sandboxed --verbose_failuresThe clean --expunge forces a full re-execution so a cached result can't mask a still-broken action. Then run a negative test: temporarily remove the input you just declared and confirm the sandbox fails again. That proves the sandbox — not luck — is what now passes.
When to escalate
File an issue against Bazel (with a minimal reproducer and your Bazel/OS versions) when any of these hold:
- The action also fails with
--spawn_strategy=local— the sandbox is not the problem. - The sandbox helper itself crashes (SIGSEGV/SIGKILL), suggesting a kernel or container incompatibility.
- The failure reproduces on a clean checkout with a supported Bazel LTS and a supported OS release.
- A mandatory security policy (SELinux, AppArmor) blocks sandbox syscalls — that needs a policy audit, not a rule change.
Before filing, check the release notes for your Bazel version; sandbox behavior (namespace handling, tmpfs defaults) changed across the 7.x and 8.x lines, and your symptom may already be a known, fixed issue.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.