external/ tree and action cache, stays behind. aspect gc removes what nothing is using.
What it removes
Each period takes
0: the grace period to remove orphans immediately, the idle cutoffs to turn their rule off. Everything removed is re-creatable: a removed output base costs a cold build, a pruned download one re-fetch.
“Last used” is when a Bazel server last started or ran a command in the base. A download-cache entry’s last use is the newer of its mtime and atime, since Bazel touches the mtime on every cache hit.
What it keeps
- An output base with a running Bazel server, unless it is orphaned. An orphan’s server is stopped before its base is removed.
- An output base a process may be serving that
pscannot identify, as on an image without procps. - An
aspect worktreepool slot’s base, which is not treated as orphaned: a free slot has no worktree on purpose, and that base is what makes its next lease warm. - The exception is a slot nothing will lease again, its directory gone and its pool holding no record of it. That base is an orphan like any other.
- A pool slot’s base still goes by idle time, a leased slot’s included, since
--base-max-idle-daysmeasures Bazel’s use of the base and not whether a checkout is open. A removed base costs that slot a cold build, never its checkout. - Anything under the output user root that is not named like an output base, such as
install/andcache/.
Removing specific output bases
Name output bases to remove them instead of applying the rules, whatever their state. Each is a hash or a path under the output user root, and any number can be named at once. A running server in a named base is stopped first. The download cache is left alone, and no census is printed; the JSON still carries the policy, with no download-cache entries.aspect output-bases lists the hashes.
Removing every output base
--all removes every output base under the output user root, whatever its state — live, idle, orphaned, within the grace period, aspect worktree pool slots, undetermined and trash — as if each were named. A running Bazel server in one is confirmed as that base’s own and stopped first; the listing, the prompt and --dry-run say which, and mark the server of the workspace you run it from. A process ps cannot identify fails that base’s removal rather than being killed.
aspect worktree checkouts, leases or registries — a pool slot just starts cold — nor install/ or cache/repos/v1/contents, and the download cache keeps its --download-cache-max-idle-days rule. Only a directory holding one of DO_NOT_BUILD_HERE, execroot/, server/ or lock is taken; any other md5-named entry is listed as kept, and a root where none holds any, with no install/ beside them — an install/ directory passed as the root, say — is refused. --all cannot be combined with named bases. Off a terminal it needs --force; on one, the prompt states what it will do: “Remove all 58 Bazel output bases (91.2 GB), stopping 12 running Bazel servers?”
Confirmation
gc lists everything it will remove, with sizes, then asks. --force answers yes in advance and is required when stdin is not a terminal, so a gc in CI or cron states that it deletes.
JSON
--output=json writes one document to stdout: the output user root, the policy, a count per state (pooled counting pool slots that are neither idle nor orphaned), each removal with its state, kb and outcome (reaped, skipped, failed, or not attempted under --dry-run or when the prompt is declined) and, where its base had a running server, server_stopped, download-cache totals, and what was freed. all says whether --all was given. kept lists the entries left alone as not output bases; both keys are always present, and kept is empty rather than absent when there is nothing to report. The exit code is non-zero when anything could not be removed. A run refused for want of --force off a terminal writes no document.
~; the document carries them in full.

