5.8.1 Symlink Forests
recommendedA sandboxed action does not run in a copy of the repository. Bazel gives it a small filesystem view shaped like the normal execution root, then materializes the action's declared inputs at their exec paths. For the ordinary symlinked sandbox implementation, most of those entries are symbolic links back to files in Bazel's real execroot. Understanding that indirection makes both sandbox failures and sandbox setup costs much easier to diagnose.1,2
Keep the two execroots separate
Bazel maintains a shared execroot under the output base. Unsandboxed local
actions work there, and its paths are the reference points for action inputs and
outputs. A sandboxed spawn instead receives a temporary directory that mimics
that layout. The action still opens paths such as pkg/source.cc or
bazel-out/.../generated.h, but those names now resolve inside its private
working tree.2,3
This per-action tree is not the repository-level symlink forest described by
the output-directory layout, nor is it a binary's runfiles tree or one of the
bazel-bin convenience links. All of those structures may use symlinks, but
they have different lifetimes and consumers. Here, “symlink forest” means the
input view prepared for one sandboxed spawn.
For each ordinary file input, Bazel knows two paths:
- the exec path at which the tool expects to see the input, and
- the backing path in the shared execroot where Bazel already has that source, generated artifact, tool, or runfile.
The symlinked sandbox creates the first path and points it at the second. Declared symlink inputs are recreated with their declared link target instead, and special empty inputs are created directly in the sandbox. Bazel also creates the necessary parent directories and writable locations for declared outputs. After the process exits, Bazel moves known outputs back to the shared output tree and discards unknown files with the sandbox.1,4
That mapping is the filesystem realization of the action contract designed in 4.4.3 Action Execution Contract. The rule supplies the inputs and tools. sandbox preparation turns that set into the process's working view. Changing execution strategy does not repair an incomplete contract—it only changes how strictly the missing input is exposed.
Symlinks stage inputs. The sandbox backend enforces policy
The symlink forest and OS isolation solve related but distinct problems. The
forest gives the process a sparse, execroot-shaped view, so an undeclared
cwd-relative path is normally absent. processwrapper-sandbox uses this view,
runs the command there, extracts declared outputs, and removes the directory.
linux-sandbox and darwin-sandbox add platform facilities such as Linux
namespaces or macOS sandbox-exec to constrain access outside that view more
strongly.1
This distinction prevents an overclaim: a symbolic link is not an access-control
boundary by itself. A tool can resolve a link to its backing path, and the
generic process wrapper does not hide the whole host filesystem. OS-specific
backends enforce stronger restrictions—for example, linux-sandbox makes the
host filesystem read-only outside writable sandbox locations and can isolate
network and process visibility—but ordinary sandboxing does not imply that
arbitrary host files are unreadable. The input tree is what presents the
declared working set. 2.3.2 Sandboxing develops the correctness model. At
this level, inspect the two mechanisms separately when behavior differs between
processwrapper-sandbox, linux-sandbox, and darwin-sandbox.
The strategy names and mnemonics from 2.5.2 Strategies & Mnemonics remain the
outer routing layer. “This was a CppCompile action using sandboxed” identifies
the action class and requested strategy. The concrete backend and its input-tree
implementation explain the filesystem behavior that followed.
The cost follows filesystem work, not target count
A fresh symlinked sandbox performs work proportional to the filesystem entries it must materialize. Ordinary file input paths require link creation, while their directory prefixes require directory creation. The special input forms described above still require their own filesystem entries. Teardown must remove those entries again. The number of BUILD targets is therefore the wrong denominator: one action with a very large toolchain or runfiles closure can cost more to stage than many small actions.4
The useful cost model is:
- compute the action's input mapping.
- create or reconcile directories and links.
- execute the tool while it reads through those links.
- extract outputs and clean or retain the sandbox directory.
Steps 2 and 4 are metadata-heavy. Their wall time depends on input-tree shape, filesystem metadata latency, platform behavior, and whether a reusable sandbox already contains compatible entries. Do not turn the linear operation count into a universal latency claim. The sandboxfs history is a useful warning: on real macOS builds, symlink creation was not always the dominant cost. Lost compiler-local state and I/O through the sandbox could matter more.4
Historical lesson: sandboxfs moved the cost
sandboxfs was an experimental alternative that exposed each action's input
mapping through a long-lived FUSE filesystem. Bazel still had to enumerate the
mapping, build a manifest, and send it to the daemon, but it avoided creating
and deleting one host filesystem entry for every mapped input. File and
metadata access then crossed the userspace filesystem while the action ran.
The design therefore moved work from view materialization into the control
path and per-access I/O; it did not make sandboxing independent of input-tree
shape.4
That trade can favor a large declared tree that the tool touches sparsely and
lose for metadata-heavy or full-tree access. In practice, operation and
maintenance complexity combined with workload-dependent performance, and
Bazel removed the integration. There is no current sandboxfs strategy or
replacement flag to enable. Current Bazel uses its maintained sandbox backends
with physical input trees, while directory reuse reduces avoidable setup for
sandboxed non-worker actions.1,5,6 Treat sandboxfs as a design experiment:
measure where a filesystem-view implementation pays, rather than assuming that
fewer symlinks means a faster action.
Reuse reconciles a previous tree
In Bazel 9.0, --reuse_sandbox_directories defaults to true for sandboxed
non-worker execution. Reuse does not mean handing the next action an unchanged
directory. Bazel may stash an old sandbox, remove entries the new action does
not need, keep links that already point at the correct backing files, and create
the missing remainder. The optimization reduces avoidable setup work while the
new action still receives its own declared input mapping.5
Reuse is a host optimization, not a declaration fix, and its benefit depends on the workload. Workers have a separate lifecycle and sandboxing model, so the flag's non-worker scope matters.5 When setup cost becomes the incident, continue with 5.8.2 Diagnosing Sandbox Issues for the profile-first comparison, the exact reuse experiment, and sandbox-base tuning. Use the mechanism above as the hypothesis those measurements can confirm or reject.
Know when this model stops applying
This article describes the ordinary symlinked per-action sandbox. It does not claim that every Bazel filesystem view uses this representation. The experimental hermetic Linux sandbox can stage ordinary input files with hard links, copying across filesystems when necessary. Persistent and multiplex workers have distinct directory lifetimes. Remote executors construct input roots using their own storage and isolation mechanisms.5
A per-action symlink forest is an execroot-shaped view in which ordinary file inputs map from declared exec paths to backing files in Bazel's shared execroot. It stages the action's view. The concrete sandbox backend supplies the stronger host restrictions.
Diagnose its performance by filesystem entries and lifecycle work, not targets.
On Bazel 9, directory reuse is already the default for sandboxed non-worker
execution and reconciles old contents with the new input mapping. Its benefit is
workload-specific. The historical sandboxfs experiment reinforces the same
rule: filesystem virtualization moves setup work into mapping and access paths
rather than eliminating it. The later diagnostic workflow tests whether setup
is material before changing execution policy.
Check your understanding · 3 questions
1.Match each sandbox layer to the role it plays for one action:
Drag each answer onto the matching prompt, or click an answer and then click a prompt
2.Which observation is the best reason to investigate symlink-forest setup cost?
Select one answer
3.Assess these claims about sandbox-directory reuse in Bazel 9:
Choose True or False for each sentence
Footnotes
-
Sandboxing —
processwrapper-sandboxsymlink setup, OS-specific enforcement, output extraction, cleanup, and reuse guidance ↩1 ↩2 ↩3 ↩4 -
Output Directory Layout — the shared execroot and the contract that sandboxed actions run in a directory that mimics it ↩1 ↩2
-
What are Bazel's strategies? — sandbox lifecycle and the separation between strategy choice and action semantics ↩
-
Whatever happened to sandboxfs? — per-action symlink-forest construction, syscall cost, and evidence that setup was not always the dominant macOS penalty ↩1 ↩2 ↩3 ↩4
-
Command-Line Reference — Bazel 9 defaults and scope for
--reuse_sandbox_directories, plus the hermetic Linux sandbox's hard-link/copy exception ↩1 ↩2 ↩3 ↩4 -
Bazel — core implementation, documentation, and regression corpus — upstream removal of the
sandboxfsintegration and the maintained strategy surface ↩