/disk-cleanup
Measure real disk usage, report it percentage-based, and rank cleanup candidates by reclaimable size × safety — never by size alone.
Tools
Both are Rust. Install if missing: brew install dust dua-cli
| Tool | Use it for |
|---|---|
dua |
Scanning. Parallel walker, ~68 GiB / 500k files in 7.5s. Best for totals and drill-down. |
dust |
Presentation. Native percentage bars — this is the report format. |
Canonical commands:
# percentage view (the deliverable)
dust -d 1 -n 15 -r ~
# totals + drill-down
dua --format binary ~
dua --format binary /Applications/* | tail -12
dua --format binary ~/Library/Application\ Support/* | tail -8
Step 1 — Establish ground truth first
df lies on APFS (snapshots, purgeable). Always cross-check:
df -h / /System/Volumes/Data
diskutil info /System/Volumes/Data | grep -i -E "free|used|container total"
Scan both ~ and /Applications, /Library, /opt. On this machine
/Applications outweighed the entire home directory — a home-only scan
misses the biggest thing.
Then compute unaccounted = volume_used - sum(measured) and show it as a row.
That row is where the truth you couldn't reach is hiding.
Step 2 — Two things that break the scan
Dead FileProvider mounts hang the walk. A signed-out cloud mount under
~/Library/CloudStorage/ makes find/dua hang forever, not error. Exclude it:
dust -X "SomeCloudMount-name" ~
dua --ignore-dirs ~/Library/CloudStorage/<mount> ~
TCC blocks ~/Pictures, ~/Library/Containers, Messages, Mail. These return
Operation not permitted and count as 0, silently. If the scan reports
IO errors, say so explicitly — the Photos library alone can be 20 GiB+ of
invisible usage. Fix: grant the terminal Full Disk Access
(System Settings → Privacy & Security → Full Disk Access), then rescan.
Never present a report with IO errors as if it were complete.
Step 3 — VERIFY EVERY CANDIDATE BEFORE PROPOSING IT
This is the step that matters. A directory's name does not tell you whether it holds live data. Check before recommending, and check again before deleting.
# Is the app actually stale, or used yesterday?
mdls -name kMDItemLastUsedDate -raw "/Applications/Foo.app"
# Container VM dirs (~/.colima, ~/.docker, ~/.lima, ~/.orbstack):
colima status; docker ps -a; docker volume ls; docker system df
Hard-won rule: ~/.colima looks like a disposable VM image, but it holds
every running container and named volume. A self-hosted app with a Postgres volume
lives there just as happily as a scratch build. Deleting it destroys the database
with no warning. docker system df reporting 0B reclaimable means nothing there
is safe to remove — check before you treat the directory as cache.
Same trap class:
~/.claude— session history, never delete. The bulk is~/Library/Application Support/Claude/vm_bundles, which is separate and regenerable.~/Library/Cachesvs~/Library/Application Support— the first is regenerable, the second is often real data. Do not lump them.- Anything under
~/Library/Mobile Documents— iCloud. Dataless placeholders report 0 blocks; deleting evicts real cloud data.
Step 4 — Classify
| Tier | Meaning |
|---|---|
| ✅ Safe | Pure cache, regenerates automatically, no state |
| ⚠️ Costed | Regenerates but re-downloads, or app must re-init |
| ❌ Awareness only | Large but live. List it, never delete it |
Always include the biggest item even when it's untouchable — knowing a game is 24% of the disk is useful. Just mark it clearly as not-for-deletion.
Step 5 — Report format
Keep it scannable. Two tables, no prose walls.
## Disk totals — X GiB used of Y GiB (Z% full, N GiB free)
| Area | Size | % of used |
|---|---:|---:|
| /Applications | 57.1 GiB | **36%** |
| ~/Library | 28.7 GiB | 18% |
| *unmeasured — system + Photos* | ~28.4 GiB | 18% |
## Top 5 cleanup candidates
| # | Target | Reclaims | Safety |
|---|---|---:|---|
| 1 | `~/Library/Caches` + `~/.cache` | **15.5 GiB** (9.7%) | ✅ Fully regenerable |
| 5 | a large game or app bundle | **38.4 GiB** (24%) | ❌ Awareness only — in active use |
Bold the reclaim figures. Give the combined safe total and the resulting percentage ("~33 GiB, takes you 82% → 68%"). Close with any measurement gap.
Step 6 — Deleting
Dry-run first: print sizes and paths, get explicit approval, then delete. Measure before and after and report actual reclaimed bytes — not the estimate.
If the user approves a candidate that Step 3 later proves unsafe, do not delete it. Execute every safe item, then say plainly what you skipped and why. Approval given on a wrong description isn't approval for the real thing.