Skip to main content
Bazel keeps an output base for every workspace path it has built in, a server for each one recently used, and every archive it has downloaded. None of it is collected. That used to accumulate slowly. It does not any more: a coding agent creates a git worktree per task, a worktree is a new workspace path, and a new workspace path is a new output base — tens of gigabytes and a Bazel server each. Delete the worktree and the output base stays, belonging to nothing and answering to nothing. Two tasks deal with it: Start with the first one. It is read-only, and on a machine that has been running agents the number is usually a surprise.

1. See what is there

Every output base, grouped by state, largest first: when it was last used, the memory its Bazel server holds, the repository and branch it is checked out on, and its workspace. The census names each state and the rule that applies to it:
That returns at once. Add --measure-sizes for disk usage per base and across the caches they share, or --sort=size to measure and order by it — both walk every file under the output user root, which takes about a minute once there are tens of gigabytes. Two numbers usually matter:
  • Removable bases: orphaned (the workspace was deleted) and idle (unused for 30 days). The listing ends with their count and size.
  • Running servers: each holds its memory until it has been idle for Bazel’s --max_idle_secs (3 hours by default). The line under the census gives the total.

2. Preview

Lists each base gc would remove and why, the stale download-cache entries, and the total it would free. Nothing is deleted.

3. Reclaim

Lists the same plan and asks before deleting. Everything it removes is re-creatable by a cold build or a re-fetch. It will not touch a base Bazel used after the scan, a running server whose workspace still exists, anything beside the bases that is not named like one, or an aspect worktree pool slot it would otherwise call orphaned. To remove a base the rules keep, such as an undetermined one, name it:

4. Tune the rules

The grace period keeps an orphaned base for an hour after its last use, so a worktree torn down and recreated at the same path gets its base back.

5. Run it on a schedule

gc asks before deleting, and refuses off a terminal unless --force says it may. A nightly cron entry on a developer machine:
Run it with the volumes your workspaces live on mounted: a workspace gc cannot find counts as gone.

Worktrees and agents

Each worktree is a new workspace path, so each gets its own output base, server and external/ tree. Deleting the worktree orphans them; gc removes them an hour after their last use. aspect worktree avoids that by reusing a pool of slots at fixed paths, each keeping one warm output base for its next lease. Worktrees covers the pool: how warm a slot is, how one is chosen for a branch, and how leases work. Pool slots carry their lease holder in the listing, so you can see which agent has what:
There is an agent skill for teaching an agent to lease a slot instead of running git worktree add. gc does not treat a pool slot’s base as orphaned, though it still removes one idle past --base-max-idle-days; aspect worktree prune deletes slots that have gone stale, base and all.

Starting over

aspect gc --all removes every output base under the root, whatever its state, stopping each one’s Bazel server first, and leaves aspect worktree checkouts, install/ and the download cache’s own rule alone. Every workspace’s next build is then cold. Preview it with --dry-run; see removing every output base.

Scripting

Both tasks take --output=json. For example, the workspaces whose bases gc would remove:

These are AXL tasks

output-bases, gc and worktree never run Bazel. They read its rc chain to resolve --output_user_root the way Bazel would, then work on what is on disk; the only processes they start are git, ps and realpath. They don’t extend the build — they improve the work around it, and they do it in AXL, the same Starlark you already write for Bazel. Ask the CLI where they come from:
@aspect is the extension library the CLI ships; .aspect/ in your repository is another, and a task in either is the same kind of object. So the shell scripts a team accumulates beside its build — the ones everyone has aliased slightly differently — can be tasks instead, written in a language the repository already uses. What you write then shows up in aspect help and aspect describe with typed arguments, which is how a person finds it and how an agent learns to call it. Writing AXL tasks covers task(), arguments and registration.