> ## Documentation Index
> Fetch the complete documentation index at: https://aspect.build/llms.txt
> Use this file to discover all available pages before exploring further.

# aspect gc

> Remove Bazel output bases whose workspace is gone or that have gone unused, and prune the download cache, with aspect gc.

Bazel keeps one output base per workspace path, and the download cache, forever. Delete a git worktree and its output base, with its `external/` tree and action cache, stays behind. `aspect gc` removes what nothing is using.

```shell theme={null}
aspect gc --dry-run      # list what would go, delete nothing
aspect gc                # list, confirm, remove
aspect gc --force        # remove without asking (CI, cron)
aspect gc <hash>...      # remove these bases, whatever the rules say
```

## What it removes

| Rule | Removed once | Flag | Default |
| - | - | - | - |
| **Orphaned** output base: its workspace directory no longer exists | unused for the grace period | `--orphan-grace-hours` | 1 |
| **Idle** output base, whether or not its workspace exists | unused past the maximum idle time | `--base-max-idle-days` | 30 |
| **Download cache** entry (`cache/repos/v1/content_addressable`) | unused past the maximum idle time | `--download-cache-max-idle-days` | 30 |
| A `.gc-trash-*` directory an interrupted `gc` left behind | always | | |

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 `ps` cannot identify, as on an image without procps.
* An `aspect worktree` pool 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-days` measures 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/` and `cache/`.

## 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.

```shell theme={null}
aspect gc d41d8cd98f00b204e9800998ecf8427e
```

[`aspect output-bases`](/docs/cli/tasks/output_bases) lists the hashes.

## 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.

```text theme={null}
$ aspect gc
INFO: Bazel output user root: ~/Library/Caches/bazel/_bazel_me
  scanning Bazel output bases…
  5 Bazel output bases: 2 live, 1 idle, 1 orphaned, 1 undetermined
    orphaned: the base's workspace directory no longer exists, e.g. a deleted worktree.
      `aspect gc` removes one 1h after last use (--orphan-grace-hours=N to change, 0 for immediately).
    undetermined: nothing in the base names its workspace, so it cannot be orphaned.
      `aspect gc` removes one 30d after last use (--base-max-idle-days=N to change, 0 to keep), or now with `aspect gc <hash>`.
    Run `aspect output-bases` to list the kept Bazel output bases (live, undetermined).
  measuring sizes of 2 Bazel output bases…
  scanning the Bazel download cache…
  2 Bazel output bases to remove, 3.3 GB:
    2.2 GB  7f18ea0edb2474de1526d9aa847f0ab9  (workspace gone: ~/src/web/.claude/worktrees/feat-search)
    1.1 GB  557089df67d989f705a1cf9794dcb086  (unused for 45d; workspace ~/src/api)
  Bazel download cache: 812 of 2,104 entries unused for 30d or more, 3.1 GB
  Delete 2 Bazel output bases and prune 812 Bazel download cache entries, freeing 6.4 GB? [y/N] y
  [1/2] removing 2.2 GB  7f18ea0edb2474de1526d9aa847f0ab9
  [2/2] removing 1.1 GB  557089df67d989f705a1cf9794dcb086
  pruning 812 Bazel download cache entries…
  reclaimed 6.4 GB
```

Each base is checked again just before it is removed and left alone if Bazel used it after the scan, so a build started while the prompt waited keeps its output base.

## 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), download-cache totals, and what was freed. 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.

```json theme={null}
{
  "schema_version": 1,
  "output_user_root": "~/Library/Caches/bazel/_bazel_me",
  "dry_run": true,
  "policy": {"orphan_grace_hours": 1, "base_max_idle_days": 30, "download_cache_max_idle_days": 30, "bazel_output_bases": []},
  "counts": {"live": 2, "idle": 1, "orphaned": 1, "orphaned_within_grace": 0, "pooled": 0, "undetermined": 1, "trash": 0},
  "removals": [
    {"path": "~/Library/Caches/bazel/_bazel_me/7f18ea0edb2474de1526d9aa847f0ab9", "workspace": "~/src/web/.claude/worktrees/feat-search", "state": "orphan", "pid": 0, "last_used_ms": 1759359600000, "kb": 2306867, "outcome": "not attempted"},
    {"path": "~/Library/Caches/bazel/_bazel_me/557089df67d989f705a1cf9794dcb086", "workspace": "~/src/api", "state": "idle", "pid": 0, "last_used_ms": 1755644400000, "kb": 1153434, "outcome": "not attempted"}
  ],
  "download_cache": {"entries": 2104, "stale_entries": 812, "stale_kb": 3250586, "pruned": 0, "skipped": 0, "freed_kb": 0},
  "reclaimable_kb": 6710887,
  "reaped": 0,
  "skipped": 0,
  "freed_kb": 0
}
```

Paths are shown here with `~`; the document carries them in full.

## Flags

| Flag | Default | |
| - | - | - |
| `--dry-run` | `false` | List what would be removed; delete nothing. |
| `--force` | `false` | Remove without asking. Required off a terminal. |
| `--orphan-grace-hours` | `1` | Hours an orphaned base is kept after its last use. |
| `--base-max-idle-days` | `30` | Days of disuse after which a base is removed. `0` turns this off. |
| `--download-cache-max-idle-days` | `30` | Days of disuse after which a download-cache entry is pruned. `0` turns this off. |
| `--output-user-root` | rc, then platform default | The output user root to collect. |
| `--output` | `text` | `text` or `json`. |
| `<hash>...` | | Output bases to remove by name, whatever their age; see [removing specific output bases](#removing-specific-output-bases). |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.