# Cleaning up

> Delete the safe tier, choose from what needs review, delete exact paths from a selection file, empty the Trash, and uninstall apps.

> [!IMPORTANT]
> Every command on this page deletes permanently. Nothing goes to the Trash. In a terminal each one shows what it will delete and asks first; see [Safety and deletion](https://diskcensus.com/docs/cli/safety.md).

## census clean

**Usage:** `census clean [PATH ...] [SCOPE] [--safe] [--progress] [--json]`

Rescans the scope, deletes the `safe` findings (caches, build output and logs that rebuild themselves), and saves a review set of everything that needs a person. In a terminal it lists the items and asks `[y/N]`.

| Option | Description |
| --- | --- |
| `--scope KIND` | `drive` (the default) or `data` |
| `-p, --path PATH` | Clean only within this folder |
| `--safe` | Delete without asking. Required without a terminal. |
| `--progress` | Write scan and deletion progress as NDJSON to stderr |
| `--json` | Write one JSON response |

```sh
# List the safe items, ask, delete
census clean

# From a script or an agent, after the person agreed
census clean --safe --json
```

```json
{
  "schema_version": 5,
  "command": "clean",
  "status": "ok",
  "exit_code": 0,
  "errors": [],
  "warnings": [],
  "results": {
    "freed_bytes": 2041856000,
    "used_before_bytes": 50146312192,
    "used_after_bytes": 48104456192,
    "audit": {
      "display": "/Users/me/Library/Application Support/census/audit/clean-1790950000000.ndjson",
      "lossy": false
    },
    "items": [
      {
        "path": { "display": "/Users/me/.npm/_cacache", "lossy": false },
        "rule": "npm.cache",
        "outcome": { "result": "deleted", "bytes": 44953600 }
      },
      {
        "path": { "display": "/Users/me/Library/Caches/com.example.app", "lossy": false },
        "rule": "app.caches",
        "outcome": {
          "result": "skipped",
          "reason": { "kind": "in_use", "detail": "Example is running" }
        }
      },
      "…"
    ],
    "review": {
      "review_id": "rv-1790950000000-1a2b3c4d",
      "items": 14,
      "allocated_bytes": 6300000000
    }
  }
}
```

`used_before_bytes` and `used_after_bytes` are the volume's used space around the run. With APFS snapshots or clones, used space can fall by less than `freed_bytes`. [Item outcomes](https://diskcensus.com/docs/cli/safety.md#outcomes) lists every result and reason. Only one cleanup runs at a time (`cleanup_running`), and `clean` refuses to run as root.

### Clean only some safe items

With `PATH` arguments, only those safe findings are deleted. Write each exactly as `census findings --json` shows its path. If any one isn't a safe finding of the fresh scan, the whole run is rejected (`invalid_selection`) and nothing is deleted.

```sh
census clean --safe --json "/Users/me/.npm/_cacache"
```

## census review

**Usage:** `census review [SCOPE] [--refresh] [--progress] [--json]`

Shows the newest review set for the scope, if it is at most 7 days old, and builds one from a fresh scan if there is none. In a terminal it is a numbered menu: `Delete which? (1,3-5 · Enter for none)`, then `Type delete`.

| Option | Description |
| --- | --- |
| `--scope KIND` | `drive` (the default) or `data` |
| `-p, --path PATH` | A folder's own scan |
| `--refresh` | Build a new review set from a fresh scan |
| `--progress` | With `--refresh`, write scan progress as NDJSON to stderr |
| `--json` | Write the review set. Choose from it with `census apply`. |

```text title="Example session"
$ census review
Needs review · 14 items · 6.3 GB
  1    3.1 GB  ~/proj/foo/target  Cargo build output
  2    1.4 GB  ~/Downloads/Xcode_15.xip  Installer in Downloads
Delete which? (1,3-5 · Enter for none): 2
Permanently delete 1 item (1.4 GB)? Type delete: delete
Freed 1.4 GB · used 44.7 GB → 43.3 GB
```

`review --json` only lists. It never deletes:

```sh
census review --json
```

```json
{
  "schema_version": 5,
  "command": "review",
  "status": "ok",
  "exit_code": 0,
  "errors": [],
  "warnings": [],
  "results": {
    "review_id": "rv-1790950000000-1a2b3c4d",
    "created_at_unix_ms": 1790950000000,
    "scan_id": "scan_1790949990000-41234",
    "items": [
      {
        "path": "/Users/me/proj/foo/target",
        "selectable": true,
        "kind": "folder",
        "rule": "cargo.target",
        "title": "Cargo build output",
        "reason": { "kind": "needs_review" },
        "allocated_bytes": 3100000000
      },
      {
        "path": "/Users/me/Downloads/Xcode_15.xip",
        "selectable": true,
        "kind": "file",
        "rule": "installers.downloads",
        "title": "Installer in Downloads",
        "reason": { "kind": "needs_review" },
        "allocated_bytes": 1400000000
      },
      "…"
    ]
  }
}
```

`path` is a plain string here: selection files use exactly these strings. `selectable: false` marks a path that isn't valid UTF-8, which only the terminal menu can choose.

## census apply

**Usage:** `census apply FILE [SCOPE] [--progress] [--json]`

Deletes the paths a selection file lists. This is how scripts and agents delete what a person chose, without a terminal menu.

| Option | Description |
| --- | --- |
| `--scope KIND` | `drive` (the default) or `data` |
| `-p, --path PATH` | A folder's own scan |
| `--progress` | Write scan and deletion progress as NDJSON to stderr |
| `--json` | Write one JSON response |

### Write a selection file

A selection file is a JSON array of paths, copied exactly from one of three listings of the same scope:

- a review item's `path`, from `census review --json`;
- a copy's `members[].path.display`, from the newest `census duplicates` search;
- an item's `items[].path.display`, from the newest `census large` listing.

```json title="selection.json"
["/Users/me/Downloads/Xcode_15.xip"]
```

```sh
census apply selection.json --json
```

The whole file is rejected, and nothing is deleted, if it isn't a non-empty array of distinct, absolute, normalized paths, or if any path isn't in one of those listings (`invalid_selection`, exit code 1). A listing older than 7 days is `review_stale` (exit code 1); no listing at all is `no_review` (exit code 7). Paths you leave out are kept.

### What apply checks

`apply` rescans, then deletes each path only if it still qualifies:

- **A review item** must still be a finding of the rule the review set lists. Otherwise it is skipped as `missing`.
- **A duplicate copy** is deleted only if the same search, run again, still finds another copy of its content that stays. If every copy was chosen, or the others changed, it is skipped as `no_other_copy`. A clone of a copy that stays is deleted with `bytes: 0`.
- **A Large Files item** must still be the same file or folder and pass the same checks. Otherwise it is skipped as `missing`, `changed_since_scan` or `not_listed`.

The result has the same fields as `clean`'s, without `review`.

## census empty-trash

**Usage:** `census empty-trash [PATH ...] [--yes] [--progress] [--json]`

Shows what is in every Trash you own, then deletes it permanently after one confirmation. That covers this Mac's Trash (`~/.Trash`), iCloud Drive's, and the Trash on each local, writable drive in `/Volumes`.

| Option | Description |
| --- | --- |
| `--yes` | Empty without asking. Required without a terminal. |
| `--progress` | Write deletion progress as NDJSON to stderr |
| `--json` | Write one JSON response. Without `--yes`, it only lists. |

```sh
# Shows the count and size, then asks:
# Permanently delete 1,218 items (8.5 GB)? [y/N]
census empty-trash
```

In a terminal it deletes exactly the items it showed; anything trashed after that stays. `--json` without `--yes` lists and deletes nothing:

```sh
census empty-trash --json
```

```json
{
  "schema_version": 5,
  "command": "empty-trash",
  "status": "ok",
  "exit_code": 0,
  "errors": [],
  "warnings": [],
  "results": {
    "count": 1218,
    "allocated_bytes": 8512000000,
    "trashes": [
      {
        "name": "This Mac",
        "path": { "display": "/Users/me/.Trash", "lossy": false },
        "count": 1190,
        "allocated_bytes": 8200000000,
        "items": [
          {
            "path": { "display": "/Users/me/.Trash/Xcode_15.xip", "lossy": false },
            "kind": "file",
            "allocated_bytes": 6900000000,
            "trashed_unix_ms": 1790950000000
          },
          "…"
        ]
      },
      {
        "name": "iCloud Drive",
        "path": { "display": "/Users/me/Library/Mobile Documents/com~apple~CloudDocs/.Trash", "lossy": false },
        "count": 14,
        "allocated_bytes": 12000000
      }
    ]
  }
}
```

To delete only some of it, pass the items' `items[].path` or whole Trashes' `trashes[].path` as arguments, exactly as `--json` lists them:

```sh
census empty-trash --yes --json "/Users/me/.Trash/Xcode_15.xip"
```

- **Nothing is forced.** Locked items, items an app has open (`in_use`), and folders another user owns stay, and are listed.
- **A Trash census can't read** stops the command before anything is deleted (`trash_unreadable`, exit code 2). Reading `~/.Trash` needs Full Disk Access for the app that runs census.
- **iCloud items** that are only in the cloud are deleted without being downloaded.

`--yes` lists the Trashes again before deleting, so it deletes what is there by then, as Finder does. With `--yes --json`, the result is `clean`'s without `review`, with `rule: "user.trash"` on every item.

## census uninstall

**Usage:** `census uninstall NAME [PATH ...] [SCOPE] [--yes] [--progress] [--json]`

Rescans, then removes an app and the data named after it. `NAME` matches as [`census app`](https://diskcensus.com/docs/cli/find.md#app) does, by name or bundle ID. In a terminal it lists what goes and asks `[y/N]`.

| Option | Description |
| --- | --- |
| `--scope KIND` | `drive` (the default) or `data` |
| `-p, --path PATH` | A folder's own scan |
| `--yes` | Uninstall without asking. Required without a terminal. |
| `--progress` | Write scan and deletion progress as NDJSON to stderr |
| `--json` | Write one JSON response |

```sh
# See what an uninstall would remove
census app Slack

# Remove the app and its data, after one confirmation
census uninstall Slack
```

- **What goes.** The app, and its data in private locations: folders named after its bundle ID, its helper's ID, or its name, with `high` or `medium` confidence, not shared, and not needing an administrator. With `PATH` arguments, exactly those paths go, as `census app NAME --json` shows them, and the app itself must be one of them.
- **What stays.** Everything else, listed in `results.kept` with a `reason`: `shared` (a group container other apps from the developer may use), `needs_admin` (under `/Library`), `may_belong_to_another_app`, `not_selected` or `not_verifiable`.
- **Before anything is deleted.** A part of macOS is refused (`system_app`). The app's launch agents in `~/Library/LaunchAgents` are unloaded. If a program inside the app is still running, nothing is deleted (`app_running`): census never quits apps.
- **Order.** The app goes first. Its data goes only if the app was deleted in full.

```sh
census uninstall Slack --yes --json
```

```json
{
  "schema_version": 5,
  "command": "uninstall",
  "status": "ok",
  "exit_code": 0,
  "errors": [],
  "warnings": [],
  "results": {
    "app": {
      "name": "Slack",
      "bundle_id": "com.tinyspeck.slackmacgap",
      "version": "4.41",
      "path": { "display": "/Applications/Slack.app", "lossy": false }
    },
    "freed_bytes": 612000000,
    "used_before_bytes": 50146312192,
    "used_after_bytes": 49534312192,
    "audit": {
      "display": "/Users/me/Library/Application Support/census/audit/uninstall-1790950000000.ndjson",
      "lossy": false
    },
    "items": [
      {
        "path": { "display": "/Applications/Slack.app", "lossy": false },
        "rule": "app.bundle",
        "outcome": { "result": "deleted", "bytes": 560000000 }
      },
      {
        "path": { "display": "/Users/me/Library/Caches/com.tinyspeck.slackmacgap", "lossy": false },
        "rule": "app.data",
        "outcome": { "result": "deleted", "bytes": 52000000 }
      }
    ],
    "kept": [
      {
        "path": {
          "display": "/Users/me/Library/Group Containers/BQR82RBBHL.com.tinyspeck.slackmacgap",
          "lossy": false
        },
        "location": "Shared group container",
        "confidence": "medium",
        "allocated_bytes": 310000000,
        "reason": "shared"
      }
    ],
    "background_items": [],
    "not_inspected": []
  }
}
```

If the app isn't deleted in full, the exit code is 4 with `app_not_removed`, and its data is left as it was. `EPERM` on the app usually means macOS's App Management protection: allow the app that runs census in System Settings › Privacy & Security.
