--- name: system-disk-cleanup description: "Inspect and clean macOS disk usage. Use for low storage, disk-full problems, finding storage hogs, checking free space, or safely freeing space." --- # Disk Cleanup for macOS ## Quick Scan ```bash df -h / # macOS shows the sealed system snapshot here — usually misleading df -h /System/Volumes/Data # THIS is the real user data volume on Apple Silicon — always check df -h | grep -v "devfs\|map " # all volumes (catches simulator runtime APFS partitions) ``` `/` on Apple Silicon is a sealed read-only snapshot. The actual user disk usage is on `/System/Volumes/Data`. Always report the Data volume to the user, not the root snapshot — they can diverge by hundreds of GB. ## Find Space Hogs — go wide first Don't just look at `~/Library`. Scan all the places that commonly hide tens of GB: ```bash # Top-level user folders du -sh ~/Projects ~/Downloads ~/Documents ~/Movies ~/Desktop ~/Music ~/Pictures 2>/dev/null # Hidden directories in home (the big surprise — often 50+ GB hidden here) du -sh ~/.[!.]* 2>/dev/null | sort -hr | head -20 # Library breakdown du -sh ~/Library/* 2>/dev/null | sort -hr | head -10 # System Library (Xcode/CoreSimulator can hide 30+ GB here, owned by root) du -sh /Library/* 2>/dev/null | sort -hr | head -10 # Applications du -sh /Applications/* 2>/dev/null | sort -hr | head -15 # Caches breakdown du -sh ~/Library/Caches/* 2>/dev/null | sort -hr | head -10 du -sh ~/.cache/* 2>/dev/null | sort -hr | head -10 ``` ## Common Hidden Hogs (in ~/.something) These are NOT in `~/Library` and are missed by default scans. Check them first. | Location | Typical Size | What It Is | Safe to Clean? | |---|---|---|---| | `~/.cache/uv` | 5–10G | uv (Python) package cache | Yes — `uv cache clean` | | `~/.cache/huggingface` | 5–20G | Downloaded ML models | Only if you don't need them locally | | `~/.cache/puppeteer` | 500M–2G | Puppeteer Chromium | Regenerates | | `~/.gradle/caches` | 10–20G | Gradle build cache | Yes — regenerates | | `~/.android/avd` | 5–30G | Android emulator disk images | Only if you don't use emulator | | `~/.expo` | 1–5G | Expo CLI cache | Yes | | `~/.bun` | 500M–2G | Bun runtime | Only if not using Bun | | `~/.rustup` | 2–5G | Rust toolchains | Only if not using Rust | | `~/.npm` | 1–5G | npm cache | `npm cache clean --force` | | `~/.claude` | 1–3G | Claude Code state | ⚠️ Careful — contains plugins/skills | ## Known Space Hogs Reference ### CocoaPods Cache `~/Library/Caches/CocoaPods/Pods` can grow to **20G+**. Safe to delete. ```bash rm -rf ~/Library/Caches/CocoaPods/Pods ``` ### CoreSimulator (TWO locations!) ```bash # User devices (apps installed grow these) du -sh ~/Library/Developer/CoreSimulator/Devices/* | sort -hr # System runtime volumes — often 15G+ EACH, separate APFS partitions du -sh /Library/Developer/CoreSimulator/Volumes/* ls /Library/Developer/CoreSimulator/Volumes/ # one folder per installed iOS version # Check what runtimes are installed xcrun simctl runtime list # Delete old runtimes via Xcode → Settings → Platforms, or: sudo rm -rf /Library/Developer/CoreSimulator/Volumes/iOS_OLDVERSION # (Xcode needs to be quit first) # Remove unavailable simulators (often does nothing if all sims are still "available") xcrun simctl delete unavailable # To shrink a specific bloated device, erase it instead of deleting: xcrun simctl erase ``` Note: A single iOS simulator device with apps installed and run can hit 10–15G on its own. List individual device sizes to find the offenders. ### Xcode Multiple Versions Users sometimes have `Xcode.app` AND `Xcode-26.4.1.app` etc — each is 4–7G. Flag duplicate Xcode installs. ### Git Worktrees with node_modules (huge for monorepos) **This is invisible to most users.** A pnpm/npm monorepo with N git worktrees has N copies of `node_modules`. Each can be 3–5G. 10 worktrees = 30–50G. ```bash # In any git repo, list worktrees git worktree list # For each, check size + safety to delete for wt in .claude/worktrees/*/; do name=$(basename "$wt") branch=$(git -C "$wt" branch --show-current 2>/dev/null) dirty=$(git -C "$wt" status --porcelain 2>/dev/null | wc -l | tr -d ' ') last=$(git -C "$wt" log -1 --format='%cr' 2>/dev/null) merged=$(git -C "$wt" merge-base --is-ancestor HEAD origin/master 2>/dev/null && echo MERGED || echo unmerged) unpushed=$(git -C "$wt" log @{u}..HEAD --oneline 2>/dev/null | wc -l | tr -d ' ') size=$(du -sh "$wt" 2>/dev/null | cut -f1) echo "$name | $branch | $size | dirty=$dirty | $last | $merged | unpushed=$unpushed" done # Remove safely (preserves branch, just removes the checkout) git worktree remove --force ``` **Always check `dirty` and `unpushed` before removing.** Worktrees with unpushed commits or uncommitted changes need user confirmation. Offer to push the branch first as a safety net. ### Stale Project Build Artifacts Scan ALL projects, not just current one: ```bash # Next.js, dist, build, turbo caches find ~/Projects -type d \( -name ".next" -o -name "dist" -o -name "build" -o -name ".turbo" \) -prune 2>/dev/null \ | xargs du -sh 2>/dev/null | sort -hr | head -15 # Python virtualenvs in projects (separate from system Python) find ~/Projects -type d \( -name ".venv" -o -name "venv" \) -prune 2>/dev/null \ | xargs du -sh 2>/dev/null | sort -hr | head -10 # All node_modules (find duplicates across worktrees) find ~/Projects -type d -name "node_modules" -prune 2>/dev/null \ | xargs du -sh 2>/dev/null | sort -hr | head -15 ``` ### Old Log Directories (the "what is this?" finds) Apps sometimes leave behind log directories that grow unbounded and are forgotten when the app is uninstalled. Look for hidden dirs in `~/` with names you don't recognize: ```bash ls -la ~/.??* | grep ^d # dot-directories ``` Examples encountered: `~/.happy/logs` (319 files, 7G, from a tool no longer in use). Always investigate before deleting (`ls`, `cat settings.json`, etc.) — show contents to the user and confirm. ## Package Manager Caches | Item | Check Size | Clean Command | |------|-----------|---------------| | pnpm | `du -sh ~/Library/pnpm/store` | `pnpm store prune` | | npm | `du -sh ~/.npm` | `npm cache clean --force` (or `rm -rf ~/.npm`) | | yarn | `du -sh ~/Library/Caches/Yarn` | `yarn cache clean` | | pip | `du -sh ~/Library/Caches/pip` | `pip cache purge` | | uv | `du -sh ~/.cache/uv` | `uv cache clean` | | CocoaPods | `du -sh ~/Library/Caches/CocoaPods` | `rm -rf ~/Library/Caches/CocoaPods/Pods` | | Gradle | `du -sh ~/.gradle/caches` | `rm -rf ~/.gradle/caches` | | HuggingFace | `du -sh ~/.cache/huggingface` | `rm -rf ~/.cache/huggingface` | **Note on pnpm:** Because pnpm hardlinks across node_modules, deleting a project's node_modules may free less actual space than `du` suggests. Don't over-promise the savings — show the actual `df` delta afterward. ## Docker (when running) ```bash docker system df # show usage docker system prune -a -f # prune everything docker builder prune -f # just build cache ``` ## Xcode / iOS ```bash # DerivedData — always safe, rebuilds rm -rf ~/Library/Developer/Xcode/DerivedData/* # Device support (old iOS device symbols) du -sh ~/Library/Developer/Xcode/iOS\ DeviceSupport ``` ## App Caches (safe to nuke) ```bash rm -rf ~/Library/Caches/Google/* # Chrome rm -rf ~/Library/Caches/com.spotify.client/* rm -rf ~/Library/Caches/ms-playwright # Playwright browsers rm -rf ~/Library/Caches/Cypress ``` ## Android SDK ```bash du -sh ~/Library/Android/sdk # 15–25G typically du -sh ~/.gradle # 10–20G du -sh ~/.android # 5–30G (mostly AVDs) # If not doing Android dev, all three can go (plus Android Studio.app in /Applications) ``` ## APFS Snapshots (rare but worth knowing) If you delete a lot but disk doesn't free, snapshots may be holding the data: ```bash tmutil listlocalsnapshots / # OS-update snapshots usually clear themselves; a reboot helps APFS settle. # To force-thin: sudo tmutil thinlocalsnapshots / 999999999999 4 ``` ## Workflow 1. **Run `df -h /System/Volumes/Data`** (NOT just `df -h /`) — that's the real usage on Apple Silicon. 2. Run the wide scan: `~/Library`, `~/.*`, `~/Projects`, `/Library`, `/Applications`. 3. For projects, scan for node_modules, .next/dist/build, .venv across ALL of `~/Projects`. 4. For monorepos with git worktrees, enumerate worktrees with dirty/unpushed status. 5. Present findings as a table with sizes and cleanup recommendations, ranked by payoff. 6. **Ask user which items to clean before deleting.** Never delete without confirmation, especially for worktrees, .happy-style mystery dirs, or anything with possible user state. 7. After cleanup, run `df -h /System/Volumes/Data` to show actual space recovered. 8. If `df` doesn't show expected savings, suspect APFS snapshots or concurrent macOS activity. ## Tips and Gotchas - **`df -h /` lies** on Apple Silicon — always check `/System/Volumes/Data` for real numbers. - **Hidden dot-dirs in `~/` hold the most surprises**: `~/.cache/*`, `~/.gradle`, `~/.android`, `~/.happy`, etc. Default `~/Library` scans miss these entirely. - **pnpm hardlinks**: deleted node_modules frees less than `du` reports. Promise the `df` delta, not the `du` sum. - **Git worktrees are silent disk hogs** in monorepos. Always enumerate before recommending. - **CoreSimulator has TWO locations** — devices (`~/Library/...`) and runtime volumes (`/Library/Developer/CoreSimulator/Volumes`, separate APFS partitions). - **Sim runtime volumes** show up as separate filesystems in `df -h` — easy to miss. - **Disk space can go DOWN during cleanup** because macOS does background writes (Spotlight reindex, etc.). Don't panic — just keep going. - **CocoaPods cache regrows fast** when iOS devs use it — don't promise it stays gone. - **OS update snapshots** sometimes hold deleted file space; reboot often helps APFS reclaim. - Some caches regenerate (npm, Chrome, uv, Gradle) — note this to user so they're not surprised when the next build is slow.