Understanding Bazel Output Directories

bazel-<workspace-name>, bazel-out, bazel-bin, bazel-testlogs, ~/.cache/bazel et al.

When a Bazel build is initiated, Bazel generates a sophisticated hierarchy of out-of-source directories and artifacts (often under ~/.cache/bazel on Linux) and then symlinks to them from the project’s source tree via bazel-<workspace-name>, bazel-out, bazel-bin and bazel-testlogs. These aren’t just clutter—they’re integral to Bazel’s architectural design and execution model. Understanding each directory’s role in a conventional Bazel build is crucial to using Bazel effectively.

#Source Tree / Repository / Workspace

It is important to clarify the distinction between the terms “repository” and “workspace” as well as their relation to the project’s source tree, as they are often used interchangeably in Bazel.

  • Repository: A directory tree containing a set of related files, such as a project’s source code, a dependency’s source code, data files, or static assets. A directory is recognized as the root of a repository by the presence of a boundary marker file, such as MODULE.bazel or the legacy WORKSPACE file. This marker establishes the root from which all internal repository paths are resolved.

  • Main Repository / Source Tree: The directory tree, marked by an aforementioned boundary file, from which Bazel is currently being executed. Outside of Bazel nomenclature, this would simply be referred to as the project’s source tree.

  • External Repository: A repository that is not the main repository but is referenced as a dependency. External repositories are defined in MODULE.bazel (or WORKSPACE) and are fetched and cached by Bazel. Their targets and files are referenced in BUILD files and Starlark code using the @repository_name prefix (e.g., @maven//:junit, @rules_go//go:def.bzl).

  • Workspace: The complete build environment, which encompasses the main repository and the set of all defined external repositories (dependencies).

For further clarification, see Repositories, workspaces, packages, and targets.

Location:

1❯ bazel info workspace
2/home/aj/code/quantile

Starlark:

  • Labels: Reference targets in the main repository with //package:target or in external repositories with @repo//package:target
  • repository_name(): Returns "@" when called from the main repository, or "@repo_name" when called from an external repository
  • ctx.label.workspace_name: In rule implementations, returns the repository name (empty string for main repository, or the external repository name)

Usage Examples:

1# Referencing targets across the workspace
2"//foo:bar"           # Main repository target
3"@maven//:junit"      # External repository target
4
5# Checking repository context
6repository_name()     # Returns "@" in main repo, "@repo" in external
7ctx.label.workspace_name  # Empty string for main repo, "repo" for @repo
1<workspace-name>/                      <== The workspace root
2├── bazel-my-project -> <..._main>     <== Symlink to execRoot
3├── bazel-out -> <...bazel-out>        <== Convenience symlink to outputPath
4├── bazel-bin -> <...bin>              <== Convenience symlink to most recent written bin dir `$(BINDIR)`
5└── bazel-testlogs -> <...testlogs>    <== Convenience symlink to the test logs directory

Docs.

Bazel populates the source tree root (main repository/workspace root) with several standard symbolic links. They serve as stable, human-readable pointers to key locations within Bazel’s complex output tree ( outputRoot). While effective for correctness and performance, the output tree paths are deeply nested, identified by hashes, and reside outside the source tree, making them cumbersome for direct developer interaction. The source tree symlinks bridge this gap, providing a consistent and convenient developer experience by abstracting away the implementation details of the output tree layout.

The symlinks include: bazel-<workspace-name>, bazel-out, bazel-bin, and bazel-testlogs.

#Output Tree (outputRoot, ~/.cache/bazel)

The following diagram is taken from the Bazel docs and may be slightly out of date.

 1/home/user/.cache/bazel/                                   <== Root for all Bazel output on a machine: outputRoot
 2└── _bazel_$USER/                                          <== Top level directory for a given user depends on the user name: outputUserRoot
 3    ├── install/
 4    │   └── fba9a2c87ee9589d72889caf082f1029/              <== Hash of the Bazel install manifest: installBase
 5    │       └── _embedded_binaries/                        <== Contains binaries and scripts unpacked from the data section of the bazel executable on first run (such as helper scripts and the main Java file BazelServer_deploy.jar)
 6    └── 7ffd56a6e4cb724ea575aba15733d113/                  <== Hash of the client's workspace root (such as /home/user/src/my-project): outputBase
 7        ├── action_cache/                                  <== Action cache directory hierarchy. This contains the persistent record of the file metadata (timestamps, and perhaps eventually also MD5 sums) used by the FilesystemValueChecker.
 8        ├── command.log                                    <== A copy of the stdout/stderr output from the most recent bazel command.
 9        ├── external/                                      <== The directory that remote repositories are downloaded/symlinked into.
10        ├── server/                                        <== The Bazel server puts all server-related files (such as socket file, logs, etc) here.
11        │   └── jvm.out                                    <== The debugging output for the server.
12        └── execroot/                                      <== The working directory for all actions. For special cases such as sandboxing and remote execution, the actions run in a directory that mimics execroot. Implementation details, such as where the directories are created, are intentionally hidden from the action. Every action can access its inputs and outputs relative to the execroot directory.
13            └── _main/                                     <== Working tree for the Bazel build & root of symlink forest: execRoot
14                ├── _bin/                                  <== Helper tools are linked from or copied to here.
15                ├── bazel-out/                             <== All actual output of the build is under here: outputPath
16                │   ├── _tmp/actions/                      <== Action output directory. This contains a file with the stdout/stderr for every action from the most recent bazel run that produced output.
17                │   ├── local_linux-fastbuild/             <== one subdirectory per unique target BuildConfiguration instance; this is currently encoded
18                │   │   ├── bin/                           <== Bazel outputs binaries for target configuration here: `$(BINDIR)`
19                │   │   │   ├── foo/bar/_objs/baz/         <== Object files for a cc_* rule named //foo/bar:baz
20                │   │   │   │   ├── foo/bar/baz1.o         <== Object files from source //foo/bar:baz1.cc
21                │   │   │   │   └── other_package/other.o  <== Object files from source //other_package:other.cc
22                │   │   │   ├── foo/bar/baz                <== foo/bar/baz might be the artifact generated by a cc_binary named //foo/bar:baz
23                │   │   │   └── foo/bar/baz.runfiles/      <== The runfiles symlink farm for the //foo/bar:baz executable.
24                │   │   │       ├── MANIFEST
25                │   │   │       └── _main/
26                │   │   │           └── ...
27                │   │   ├── genfiles/                      <== Bazel puts generated source for the target configuration here: `$(GENDIR)`
28                │   │   │   └── foo/bar.h                  <== such as foo/bar.h might be a headerfile generated by //foo:bargen
29                │   │   └── testlogs/                      <== Bazel internal test runner puts test log files here
30                │   │       └── foo/
31                │   │           ├── bartest.log            <== such as foo/bar.log might be an output of the //foo:bartest test with
32                │   │           └── bartest.status         <== foo/bartest.status containing exit status of the test (such as PASSED or FAILED (Exit 1), etc)
33                │   └── host/                              <== BuildConfiguration for build host (user's workstation), for building prerequisite tools, that will be used in later stages of the build (ex: Protocol Compiler)
34                └── <packages>/                            <== Packages referenced in the build appear as if under a regular workspace

The output tree, officially termed the outputRoot and often found at ~/.cache/bazel on Linux, serves as the centralized location for all of Bazel’s persistent state on a machine, residing entirely outside any project’s source tree. The key design goals of this directory are:

  • Isolation: Prevent interference between different users on the same machine, different workspaces, and different target configurations (e.g., fastbuild, dbg) within the same workspace.
  • Centralization: Centralize all build state under a dedicated root directory to avoid collisions with other development tools and make cleanup simple, whether for a single project or for all of a user’s Bazel activity.
  • Accessibility: Provide stable, cached access to build artifacts.
  • Clarity: Ensure each build’s context is unambiguous.

Location:

1❯ bazel info output_base | tail -1 | xargs dirname | xargs dirname
2/home/aj/.cache/bazel

#outputUserRoot (_bazel_$USER)

Directly beneath the outputRoot, Bazel creates the outputUserRoot, a directory typically named _bazel_$USER. This directory serves as the primary mechanism for isolating the build environments of different users on a shared machine. By segregating all outputs, caches, logs, etc. on a per-user basis, Bazel prevents permission errors, cache poisoning, and other state-related conflicts that could arise in a multi-tenant development environment.

Location:

1❯ bazel info output_base | tail -1 | xargs dirname
2/home/aj/.cache/bazel/_bazel_aj

CLI Options:

  • --output_user_root=<path>: Startup option that overrides the default outputUserRoot location. Useful for isolating user builds in non-standard environments.

Usage Examples:

1# Use a custom output user root directory
2bazel --output_user_root=/custom/path build //...

#repository_cache

The repository_cache is a content-addressable storage directory for external dependency artifacts fetched by repository rules, such as http_archive or http_file. Located under the outputUserRoot, this cache is intentionally shared across all of a user’s workspaces, rather than being tied to a specific project’s output_base. This design promotes efficiency by preventing redundant downloads of the same dependency version across different projects.

When a rule defines a dependency with a SHA256 hash, Bazel downloads the corresponding archive, verifies its integrity against the provided hash, and then stores it in the repository_cache. The artifact is typically stored using its hash as its identifier. This mechanism is fundamental to ensuring build hermeticity and reproducibility; it guarantees that any build requesting that specific dependency hash will receive the exact same bits, insulated from upstream changes or network issues. Subsequent build or fetch operations that require the same artifact will retrieve it directly from this local, verified cache, significantly accelerating dependency resolution.

Unlike most build outputs, this cache is not affected by the bazel clean command. This design ensures that if workspace build outputs are cleaned, subsequent builds will not incur the cost of re-downloading large dependency archives. For situations requiring a complete purge of downloaded artifacts, such as to resolve a corrupted entry or free disk space, the cache must be removed manually.

Note: Not all repository rules populate the repository_cache. Many language-specific ecosystem rules, such as Gazelle’s go_repository for Go dependency management, eschew Bazel’s native repository caching mechanism and instead implement their own logic to fetch and manage source modules directly.

For environments with restricted network access or for creating fully air-gapped builds, Bazel supports pre-populating this cache.

Location:

1❯ bazel info repository_cache
2/home/aj/.cache/bazel/_bazel_aj/cache/repos/v1

CLI Options:

  • --repository_cache=<path>: Explicitly sets the location for the shared cache.
  • --distdir=<path>: Specifies a directory of tarballs that Bazel will consult before attempting network downloads.

Usage Examples:

1# Use a custom repository cache location
2bazel build --repository_cache=/fast/ssd/bazel-repo-cache //...

Starlark:

Repository rules populate both the repository_cache (with downloaded archives) and the external directory (with extracted contents):

  • repository_rule(): Function used in .bzl files to define new types of external repositories
  • repository_ctx: Context object passed to a repository rule’s implementation function, providing the API for interacting with the outside world:
    • repository_ctx.download(): Fetches a file from a URL and stores it in the repository cache
    • repository_ctx.download_and_extract(): Downloads an archive to the cache and extracts it
    • repository_ctx.execute(): Runs a command (often to process downloaded files)
    • repository_ctx.path(): Resolves a path to a file or dependency
    • repository_ctx.file(): Writes content to a file (often a generated BUILD.bazel file)

Usage Example:

Defining a custom repository rule:

 1def _my_repo_impl(repository_ctx):
 2    # Download a file (stored in repository_cache)
 3    repository_ctx.download(
 4        url = "https://example.com/archive.tar.gz",
 5        sha256 = "abc123...",
 6    )
 7
 8    # Execute a command to extract it
 9    repository_ctx.execute(["tar", "-xzf", "archive.tar.gz"])
10
11    # Create a BUILD file for the repository
12    repository_ctx.file("BUILD.bazel", """
13package(default_visibility = ["//visibility:public"])
14
15cc_library(
16    name = "lib",
17    srcs = glob(["src/*.c"]),
18    hdrs = glob(["include/*.h"]),
19)
20""")
21
22my_repository = repository_rule(
23    implementation = _my_repo_impl,
24    attrs = {
25        "url": attr.string(mandatory = True),
26    },
27)

#install_base

The install_base directory serves as the unpacked, version-specific installation root for the Bazel binary itself. While Bazel is distributed as a single executable file, that binary is a self-extracting archive containing all the necessary runtime components—such as the core Java server, built-in tools, and standard repository definitions—required to bootstrap a build environment. The install_base is where these components are placed upon the first execution of a specific Bazel version. If this directory becomes corrupted, it can be safely deleted; Bazel will simply re-extract its contents on the next run.

The name of the install_base directory is a hash derived from the Bazel binary’s internal installation manifest. This hashing mechanism is critical for version isolation. If a developer switches between Bazel 7.0.0 and 7.1.0 on the same machine, each version will create its own distinct install_base, preventing conflicts between their respective runtime files. This design ensures that a project’s build behavior remains consistent and tied to the specific toolchain version being used, a key aspect of hermeticity.

Location:

1❯ bazel info install_base
2/home/aj/.cache/bazel/_bazel_aj/install/46f3f056d0f57434387d649d615bcaca

#output_base

The output_base directory is the per-workspace silo within the outputUserRoot where Bazel stores a workspace’s execution root ( execRoot), action cache ( action cache), external repositories ( external), server logs ( server), and more. Its name is a hash of the absolute path to the source tree root ensuring the same workspace path always gets same output_base and guaranteeing that two separate checkouts of the same project will not interfere with each other’s build caches or artifacts.

This directory is the primary target of the bazel clean command. A standard bazel clean purges generated artifacts and cache entries within the output_base but preserves the overall directory structure. For a complete reset of a workspace’s state, the bazel clean --expunge command can be used to remove the entire output_base directory itself. Upon the next build invocation, Bazel will create a new, empty output_base, effectively starting the project’s build state from scratch.

Location:

1❯ bazel info output_base
2/home/aj/.cache/bazel/_bazel_aj/e26182728276e2bc4cf77699f842469d

CLI Options:

  • --output_base=<path>: This startup option overrides the default hash-based directory name. It forces all outputs for the current workspace into a specific, user-defined path, which is highly useful for CI systems that need predictable paths.
  • bazel clean --expunge_async: For very large repositories, this performs the expunge operation in the background, returning control to the terminal much faster.

Usage Examples:

1# Run a build using a custom output base
2bazel build --output_base=/mnt/ssd/bazel_outputs/my_project //...
3
4# Clean large repositories asynchronously
5bazel clean --expunge_async
#action_cache

The action_cache is a persistent, on-disk key-value store that memoizes the results of previously executed build actions. Its primary function is to enable Bazel’s highly performant and correct incremental build model.

Before executing any action (e.g., compiling a source file, linking a binary), Bazel computes a unique cache key that consists of multiple components:

  • actionKey: A hash of non-file data including the action’s command line arguments and mnemonic
  • usedClientEnvKey: A hash of environment variables from the --action_env flag
  • digestKey: A hash of the action’s input and output files

When Bazel considers executing an action, it checks the action cache entry against its current in-memory version. If all digest fields match, the outputs in the output tree are valid and the action need not be rerun. This provides efficient incremental builds by skipping unnecessary work.

However, the action cache has limitations. It doesn’t retain entries across certain state changes. For example, modifying a file, rebuilding, then reverting the change will result in three builds rather than recognizing the third state matches the first. This is because the action cache is relatively short-lived. To address this limitation and enable cache sharing across machines, Bazel supports remote caching via the --disk_cache and --remote_cache flags.

The cache itself is stored in an internal, opaque binary format and is not intended for direct user inspection. Its contents can be dumped via bazel dump --action_cache and cleared via bazel clean.

Location:

1echo "$(bazel info output_base)/action_cache"
2/home/aj/.cache/bazel/_bazel_aj/e26182728276e2bc4cf77699f842469d/action_cache

CLI Options:

  • --disk_cache=<path>: Configures a separate, shareable on-disk cache. Unlike the internal action_cache, this cache can be shared by different workspaces on the same machine.
  • --remote_cache=<url>: Specifies the URL of a remote build cache, extending the action cache concept across a team and CI infrastructure.
  • --noremote_upload_local_results: When using a remote cache, prevents locally-run action results from being uploaded, useful for preventing a developer’s machine from “poisoning” a golden CI cache.

Related Tools:

Use aquery (action graph query) to inspect the inputs that form an action’s key:

1# See the inputs for a C++ compile action, which determine its cache key
2bazel aquery 'mnemonic("CppCompile", //path/to:target)' --output=jsonproto

Starlark:

The Starlark API doesn’t interact with the action cache directly, but it defines the inputs that create the cache key for every action:

  • ctx.actions.run() and ctx.actions.run_shell(): Core functions for defining build actions. The action’s cache key is derived from:
    • inputs: List of input File objects (content is part of the key)
    • outputs: List of declared output File objects
    • command/arguments: The command to execute
    • executable: The tool being run (contents are part of the key)
    • env: Environment variables passed to the action
    • execution_requirements: Tags that modify execution, like {"no-sandbox": "1"}

Usage Examples:

 1def _my_rule_impl(ctx):
 2    output = ctx.actions.declare_file(ctx.label.name + ".out")
 3
 4    # All these parameters contribute to the action's cache key
 5    ctx.actions.run_shell(
 6        inputs = [ctx.file.src],
 7        outputs = [output],
 8        command = "process_file %s > %s" % (ctx.file.src.path, output.path),
 9        env = {"BUILD_MODE": "release"},
10        execution_requirements = {"no-remote": "1"},
11    )
12
13    return [DefaultInfo(files = depset([output]))]
#execRoot (bazel-<workspace-name>)

The execution root, or execRoot, is the base directory used by Bazel to create sandboxed execution environments for build actions. Its fundamental purpose is to create a consistent and hermetic build environment by assembling a complete, self-contained “symlink forest”. This directory is what the bazel-<workspace-name> convenience symlink in the source tree root points to.

The execRoot is constructed by symlinking several key components into a single hierarchy:

  • Main repository: The _main directory, which contains a complete representation of the main repository (hence the directory name _main)
  • External repositories: The _main/external directory, which contains symlinks to the unpacked contents of all external repositories required by the build
  • Build outputs: The _main/bazel-out directory, which contains all generated build outputs ( bazel-bin, bazel-genfiles, bazel-testlogs)

Location:

1❯ bazel info execution_root
2/home/aj/.cache/bazel/_bazel_aj/e26182728276e2bc4cf77699f842469d/execroot/_main

CLI Options:

  • --subcommands (or -s): Prints the exact command line for each action executed, with paths shown relative to the execRoot. Invaluable for debugging rule behavior.
  • --verbose_failures: On action failure, prints the full command and environment variables, providing a complete snapshot of the execRoot context.

Starlark:

This is the single most important concept for a rule author. The entire rule implementation function operates within the context of the execRoot:

  • File.path: For any File object (input or output), the .path attribute returns its path relative to the execRoot. This is the string you must use in an action’s command line.

Usage Examples:

In a rule implementation:

 1def _my_rule_impl(ctx):
 2    input_file = ctx.file.src  # A File object
 3    output_file = ctx.actions.declare_file(ctx.attr.name + ".out")
 4
 5    # input_file.path might be "my/pkg/source.c"
 6    # output_file.path will be something like "bazel-out/k8-fastbuild/bin/my/pkg/target.out"
 7    ctx.actions.run_shell(
 8        inputs = [input_file],
 9        outputs = [output_file],
10        # Note how .path is used to get the string path for the shell command
11        command = "cp %s %s" % (input_file.path, output_file.path),
12    )
13    return [DefaultInfo(files = depset([output_file]))]

Make Variables:

  • $(execpath ...): Provides the robust way to get the execRoot-relative path for a dependency inside a genrule
  • $(location ...): Relative to the bin directory
#output_path (bazel-out)

The output_path, accessible via the bazel-out convenience symlink in the source tree root, is the designated directory within the execRoot for all artifacts generated during the build process. Its primary function is to isolate build outputs based on their target configuration, preventing conflicts and ensuring correctness when building for multiple platforms or with different compilation flags.

Instead of placing all artifacts into a single flat directory, Bazel creates a distinct subdirectory within output_path for each unique combination of build flags, toolchains, and platform settings. These configuration-specific directories typically follow a [platform]-[cpu]-[compilation_mode] pattern, such as k8-fastbuild for a non-optimized Linux build (the AMD K8 Hammer was the first implementation of the AMD64 64-bit extension to the x86 instruction set architecture) or darwin-arm64-opt for an optimized build on Apple Silicon. A special host configuration directory is used for building tools that run on the build machine itself as part of a larger build.

Location:

1❯ bazel info output_path
2/home/aj/.cache/bazel/_bazel_aj/e26182728276e2bc4cf77699f842469d/execroot/_main/bazel-out

CLI Options:

  • --compilation_mode (or -c): Sets the build mode to fastbuild (default), dbg (debug), or opt (optimized)
  • --cpu: Specifies the target CPU architecture for cross-compilation
  • --config=<name>: Activates a named configuration from .bazelrc (e.g., build:ci --copt=-g). This is the standard way to create distinct, reusable configurations
  • --platforms=<label> and --host_platform=<label>: Modern, platform-aware flags providing more precise control than --cpu

Usage Examples:

1# This will create and use the 'k8-opt' output directory
2bazel build -c opt //...
3
4# Use a custom configuration, creating its own output directory
5bazel build --config=ci //...

The bazel-bin directory is a convenience symlink that provides a stable, user-facing pointer to the location of final build artifacts. While bazel-out is the root for all configuration-specific outputs, bazel-bin points directly into the bin/ subdirectory of the most recently used configuration. This abstracts away the complexity of the configuration path (e.g., k8-fastbuild), offering a predictable location for developers and scripts to find executables, libraries, and other deployable outputs.

This directory is populated with the primary outputs of binary and library rules and the directory structure mirrors the package structure of the source tree. For each executable target, Bazel also generates a corresponding .runfiles directory, which is a symlink farm containing all the transitive runtime data dependencies, shared libraries, configuration files, and other resources required by the binary.

Internally, build rules refer to this location using the $(BINDIR) make variable. This variable resolves to the appropriate configuration-specific path, ensuring that rule implementations can place their outputs in the correct location without needing to be aware of the full output_path structure. The bazel-bin symlink is simply the developer-friendly entrypoint to this location.

Location:

1❯ bazel info bazel-bin
2/home/aj/.cache/bazel/_bazel_aj/e26182728276e2bc4cf77699f842469d/execroot/_main/bazel-out/k8-fastbuild/bin

Related Tools:

  • cquery (configured query): Find exact paths to outputs

Usage Examples:

1# Get the exact path to the output file(s) for a target
2bazel cquery //my:binary --output=files
3
4# Query the runfiles directory contents for a target
5bazel cquery "runfiles(//path/to:target)" --output=files

Make Variables:

  • $(location ...): Resolves to the full path within bazel-bin
1genrule(
2    name = "process_binary",
3    srcs = [":my_binary"],
4    outs = ["processed.txt"],
5    # $(location) expands to the path in bazel-bin/...
6    cmd = "./my_tool --input $(location :my_binary) > $@",
7    tools = [":my_tool"],
8)

Starlark:

These directories are where a rule’s outputs are placed. The Starlark API provides mechanisms to declare files within these locations:

  • ctx.actions.declare_file(filename): Creates an output File object. Bazel places this file in a package-relative path inside the configuration-specific output directory (e.g., bazel-out/k8-fastbuild/bin/my/pkg/filename)
  • ctx.bin_dir and ctx.genfiles_dir: File objects representing the $(BINDIR) and $(GENDIR) directories. Their .path attributes can construct paths for complex output layouts
  • DefaultInfo(files=...): Signals primary outputs by returning this provider with a depset of File objects

Usage Examples:

Declaring and creating outputs in a rule implementation:

 1def _my_rule_impl(ctx):
 2    # Declare outputs that will be placed in bazel-bin
 3    main_output = ctx.actions.declare_file(ctx.label.name + ".out")
 4    secondary_output = ctx.actions.declare_file(ctx.label.name + ".log")
 5
 6    # Create the outputs
 7    ctx.actions.run_shell(
 8        outputs = [main_output, secondary_output],
 9        command = """
10            echo "Processing..." > %s
11            echo "Log data" > %s
12        """ % (main_output.path, secondary_output.path),
13    )
14
15    # Return outputs so other rules can depend on them
16    return [DefaultInfo(files = depset([main_output, secondary_output]))]

The bazel-genfiles convenience symlink is a legacy component maintained primarily for backward compatibility. In modern versions of Bazel (post-0.25), its target is identical to that of bazel-bin, and for all practical purposes, the two are interchangeable.

Historically, Bazel employed a bifurcated output strategy within each configuration directory. The bin/ subdirectory was reserved for compiled artifacts like executables and libraries, while a parallel genfiles/ directory was designated for intermediate outputs, particularly generated source code (e.g., from protoc or custom genrule actions). Rules would use $(BINDIR) to refer to the former and $(GENDIR) for the latter.

To simplify the output tree model and reduce ambiguity for rule authors, this distinction was removed. All build outputs—both compiled binaries and generated sources—are now consolidated into the bin/ directory. This creates a single, predictable location for all artifacts produced by a given target.

Location:

1❯ bazel info bazel-genfiles
2/home/aj/.cache/bazel/_bazel_aj/e26182728276e2bc4cf77699f842469d/execroot/_main/bazel-out/k8-fastbuild/bin

The bazel-testlogs symlink points to the canonical location for all artifacts generated by test executions. It serves as the primary interface for developers and continuous integration systems to access detailed results, logs, and other outputs for post-test analysis. Similar to bazel-bin, this is a dynamic symlink whose target is updated after each bazel test command to point to the testlogs subdirectory within the relevant configuration’s output path (e.g., bazel-out/k8-fastbuild/testlogs).

The directory structure within bazel-testlogs mirrors the package hierarchy of the source workspace, making it easy to locate the results for a specific test target. For each test target, this directory contains several key files:

  • test.log: Captures the raw standard output and standard error streams from the test process, making it the primary resource for debugging failures.
  • test.xml: A structured report in JUnit XML format, designed for consumption by CI systems and other test reporting tools to parse test suite results, timings, and failure statuses.
  • test.cache_status: A simple text file indicating whether the test result was a cache hit (local or remote) or if it was executed (not_cached).

Beyond these standard files, Bazel provides a test.outputs subdirectory for tests that produce undeclared output artifacts, such as screenshots on a UI test failure or performance data dumps. For tests configured to run with flaky attempt retries (via --flaky_test_attempts), this directory will also contain logs for each individual attempt.

Location:

1❯ bazel info bazel-testlogs
2/home/aj/.cache/bazel/_bazel_aj/e26182728276e2bc4cf77699f842469d/execroot/_main/bazel-out/k8-fastbuild/testlogs

CLI Options:

  • --test_output=all | errors | streamed: Controls test log verbosity. streamed prints in real-time, all saves everything to test.log, errors only on failure
  • --cache_test_results=no: Forces tests to re-run and generate fresh logs, even if inputs haven’t changed
  • --test_summary=detailed | short | terse: Controls the format of the test summary printed to console

Related Tools:

  • query: Find test targets whose logs would appear here

Usage Examples:

1# Find all test rules in the workspace
2bazel query 'kind(".*_test rule", //...)'

Notes:

  • The test_env attribute on test rules (like sh_test) allows passing environment variables into the test execution environment, whose effects can be observed in test.log

Starlark:

A rule author doesn’t directly write to this directory. Instead, they create a test rule that, when executed by bazel test, has its outputs captured and placed here by the test runner:

  • DefaultInfo(executable=...): For a rule to be a test (e.g., *_test), it must provide an executable file in its DefaultInfo provider
  • testing.TestEnvironment(env): Provider used to pass environment variables to the test runner

Usage Examples:

Creating a custom test rule:

 1def _my_test_impl(ctx):
 2    # Create the test executable
 3    test_script = ctx.actions.declare_file(ctx.label.name)
 4    ctx.actions.write(
 5        output = test_script,
 6        content = """#!/bin/bash
 7echo "Running test..."
 8if [ "$TEST_MODE" = "strict" ]; then
 9    echo "Strict mode enabled"
10fi
11exit 0
12""",
13        is_executable = True,
14    )
15
16    # Return the executable and test environment
17    return [
18        DefaultInfo(executable = test_script),
19        testing.TestEnvironment({"TEST_MODE": "strict"}),
20    ]
21
22my_test = rule(
23    implementation = _my_test_impl,
24    test = True,  # This makes it a test rule
25)
#external

The external directory, located directly within the output_base, serves as the staging area for the unpacked contents of all external repositories required by the workspace. It is the concrete on-disk representation of the third-party dependencies that are conceptually referenced in BUILD files using the @repository_name//path/to:target syntax.

This directory is populated by Bazel’s repository rules, which are defined in the MODULE.bazel or legacy WORKSPACE file. When Bazel resolves a dependency managed by a rule like http_archive or git_repository, it fetches the specified artifact—often storing the compressed archive in the shared repository_cache—and then unpacks it into a subdirectory here. The name of this subdirectory directly corresponds to the repository’s declared name (e.g., com_google_protobuf for the repository named @com_google_protobuf).

In addition to user-defined dependencies, this directory also houses special repositories that are essential for Bazel’s own operation. These include @bazel_tools, which contains built-in build rules and tools, and various auto-configuration repositories like @local_config_cc that are generated to detect and configure the host system’s toolchains.

A critical distinction exists between this directory and the repository_cache. The repository_cache stores the immutable, downloaded archives, while the external directory contains the mutable, extracted source trees that build actions will actually consume via symlinks in the execRoot. Consequently, this directory persists through a bazel clean command (to avoid the cost of re-extracting archives) but is removed by bazel clean --expunge, which forces a complete refetch and re-extraction of all external dependencies.

Location:

1echo "$(bazel info output_base)/external"
2/home/aj/.cache/bazel/_bazel_aj/e26182728276e2bc4cf77699f842469d/external

CLI Options:

  • --override_repository=<repo_name>=<local_path>: Replaces an external repository with a local directory. Essential for developing a dependency and its consumer in parallel without pushing changes.

Usage Examples:

1# Override an external dependency with local development version
2bazel build --override_repository=com_google_protobuf=/home/user/protobuf //...

Related Tools:

  • query: List all defined external dependencies
1# List the labels of all direct external dependencies
2bazel query 'deps(//external:*)'

Starlark:

The contents of this directory are created by repository rules, which first download archives to the repository_cache and then extract them here. See the repository_cache section for details on the Starlark API for repository rules.

#command_log

The command.log file, located directly within the output_base, serves as a direct transcript of the standard output and standard error streams from the most recent Bazel command invocation. This file is intentionally ephemeral; its content is completely overwritten each time a new Bazel command (such as build, test, or query) is executed within the workspace.

Its primary utility is for post-hoc analysis of the last command that was run, which is particularly useful in non-interactive environments like CI/CD pipelines or for examining the output of a command after the terminal buffer has been cleared. The log captures the same information that is printed to the console during a run: progress indicators, INFO messages, warnings, error diagnostics, and the final build summary.

It is important to distinguish this log from other logging mechanisms within Bazel. The command.log provides a high-level summary of the overall invocation, not the granular, per-action output. It is also distinct from the structured test results in bazel-testlogs and the persistent Bazel server logs found in the server/ directory. Because it resides in the output_base, the command.log survives a bazel clean but is deleted by bazel clean --expunge.

Location:

1❯ bazel info command_log
2/home/aj/.cache/bazel/_bazel_aj/e26182728276e2bc4cf77699f842469d/command.log
#command.profile.gz

The command.profile.gz file is a gzipped, JSON-formatted trace file containing detailed performance metrics from the most recent Bazel command. It is the primary mechanism for diagnosing and optimizing build performance. This file is automatically generated by default for commands like build and test and is overwritten with each new invocation. It captures a granular log of trace events throughout the build process, including:

  • Phase Timings: Time spent in distinct phases like loading, analysis, and execution.
  • Critical Path Analysis: The sequence of dependent actions that determines the overall build duration.
  • Action-Level Metrics: The start time, duration, and resource consumption of every individual build action.
  • System Telemetry: Information on memory usage, garbage collection, and CPU utilization.

Location:

1echo "$(bazel info output_base)/command.profile.gz"
2/home/aj/.cache/bazel/_bazel_aj/e26182728276e2bc4cf77699f842469d/command.profile.gz

Related Tools:

  • bazel analyze-profile: Primary tool for consuming profile data

Usage Examples:

 1❯ bazel analyze-profile "$(bazel info output_base)/command.profile.gz"
 2
 3=== PHASE SUMMARY INFORMATION ===
 4
 5Total launch phase time                              0.005 s    0.31%
 6Total init phase time                                0.042 s    2.68%
 7Total target pattern evaluation phase time           0.001 s    0.07%
 8Total interleaved loading, analysis and execution phase time    1.549 s   96.89%
 9Total finish phase time                              0.000 s    0.04%
10---------------------------------------------------------------------
11Total run time                                       1.599 s  100.00%
12
13Critical path (1.490 s):
14       Time Percentage   Description
15     891 ms   59.81%   action 'Building Hugo site @@//site/learn site'
16     599 ms   40.19%   action 'Building Hugo site @@//site/root site'
17    0.05 ms    0.00%   runfiles for //site/root serve

More analysis tools can be found in the JSON Trace Profile docs.

#sandbox

The sandbox directory, located within the output_base, is where Bazel creates the ephemeral, isolated filesystem environments used to execute individual build actions. Its existence is fundamental to Bazel’s core principle of hermeticity, ensuring that build actions are shielded from the host system and can only access their explicitly declared inputs. This guarantees reproducibility and correctness by preventing undeclared dependencies or variations in the host environment from influencing the build result.

For each action it executes, Bazel dynamically creates a unique temporary subdirectory within the sandbox directory. Inside this subdirectory, it constructs a minimal, self-contained execution root by symlinking in only the action’s declared input files, required tools, and necessary output directories. The build command (e.g., a compiler invocation) is then executed with this temporary directory as its root, effectively blinding it to any other files on the filesystem.

Under normal operation, this process is entirely transparent. Upon successful completion of an action, Bazel moves the generated artifacts from the sandbox to their final destination within bazel-out and immediately deletes the temporary sandbox directory. As a result, the top-level sandbox directory is typically empty after a build completes.

However, it becomes a critical debugging tool when an action fails. By running a build with the --sandbox_debug flag, developers can instruct Bazel to preserve the sandbox environment for any failed action. This allows for post-mortem analysis of the isolated filesystem, making it possible to verify which files were present and debug issues related to missing inputs or incorrect paths.

Location:

1echo "$(bazel info output_base)/sandbox"
2/home/aj/.cache/bazel/_bazel_aj/e26182728276e2bc4cf77699f842469d/sandbox

CLI Options:

  • --sandbox_writable_path=<path>: Allows sandboxed actions to write to specified host filesystem paths.
  • --sandbox_block_path=<path>: Explicitly prevents sandboxed actions from accessing given paths.
  • --subcommands (or -s): Prints the exact command being run inside the sandbox.

Usage Examples:

1# Allow sandbox to write to specific directory
2bazel build //my:target --sandbox_writable_path=/tmp/myapp
#server_log

The server_log is the primary diagnostic log for the persistent Bazel server process itself. Unlike command.log, which captures the high-level output of a specific build command, the server_log records the low-level internal operations of the underlying JVM that hosts the server. Its contents are intended primarily for debugging the Bazel application, not for analyzing a user’s build.

The log file contains a verbose stream of events related to the server’s lifecycle and health. This includes server startup arguments, process ID, memory management details such as garbage collection cycles and heap usage, internal data structure operations like Skyframe graph evaluations, and RPC communications between the Bazel client and server. It is also the destination for any unhandled exceptions or stack traces generated within the server process.

A new log file is generated each time the Bazel server process is started, and its filename is structured to be unique, typically incorporating the hostname, username, timestamp, and server process ID.

Location:

1❯ bazel info server_log
2/home/aj/.cache/bazel/_bazel_aj/e26182728276e2bc4cf77699f842469d/java.log.aj.aj.log.java.20250725-191350.579321
#server

The server directory, located within the output_base, contains the state and communication files for the persistent Bazel server process. Of these files, jvm.out is the most useful to users as it captures the raw standard output and error streams from the server’s JVM.

Location:

1echo "$(bazel info output_base)/server"
2/home/aj/.cache/bazel/_bazel_aj/e26182728276e2bc4cf77699f842469d/server

Recent posts

Subscribe, unless you hate fun

Get the latest posts, updates, and half-decent advice — straight to your inbox. No spam. Just vibes.