A single zsh script that finds reclaimable disk space on a Mac and removes only the things that rebuild themselves. It reports first and deletes nothing until you ask it to, and it asks before every individual deletion.
Typical first run on a developer machine recovers 20 to 40 GB.
The script measures every known cache location on your Mac, prints them largest first, and then walks the list asking whether to empty each one. It works in three tiers.
Safe tier covers app caches, system logs, crash reports, saved window state, the Trash, the Homebrew download cache, and the downloaded wallpaper cache. All of it regenerates by itself and none of it costs you anything to lose.
Dev tier covers build and package caches: Xcode DerivedData, iOS, watchOS and tvOS device support, the SwiftUI previews cache, CoreDevice data, simulator caches, CocoaPods, Swift Package Manager, Carthage, npm, pnpm, yarn, Gradle, Maven, Cargo, Composer, NuGet, Go modules, pip, Poetry, uv, Bun, Deno, Flutter, RubyGems, Playwright and Puppeteer browser downloads. These also regenerate, but the next build or install has to download again, so the cost is time rather than data.
Electron tier scans every app in Application Support for Chromium cache folders, so Teams, Slack, Notion, VS Code, Cursor, Postman and similar apps get their caches cleared without you naming them. This tier is usually worth several gigabytes on its own and it is the one most cleanup scripts miss.
It also finds node_modules folders that have not been touched in 60 days, anywhere in your home folder, and offers to remove them as a group.
Cache locations are detected rather than assumed. The script asks npm, pnpm, yarn and go where their caches live, and it honours GRADLE_USER_HOME, CARGO_HOME, COMPOSER_HOME, NUGET_PACKAGES and RUSTUP_HOME. Overlapping paths are collapsed so nothing is counted twice.
Two things it prints but never deletes. First, a context section showing your largest home folders, your largest Library folders, and every file over 1 GB, with iCloud placeholder files labelled as such so you do not chase space that is not there. Second, a review list of large items that are not safe to delete blindly, each with the correct way to handle it: simulator devices, Android system images and NDK versions, Docker data, iPhone backups, Mail, Messages, the Photos library and Rust toolchains.
macOS, and zsh, which has shipped as the default shell since Catalina. No Homebrew, no dependencies, nothing to install.
Download the script, then run it once to see the report:
zsh mac-cleanup.shNothing is deleted by that run. When you are happy with the list, run it again with --clean:
zsh mac-cleanup.sh --cleanThe script now stops at each item, tells you what it is and what losing it costs, and waits. Type y and press Enter to empty it, or press Enter to skip. Quit Xcode, your editors and your desktop apps first, since several targets belong to them.
Options:
--clean Ask about each item and delete the ones you approve
--yes, -y Answer yes to everything. Combine with --clean
--safe-only Restrict to the safe tier, skipping build and app caches
--days N Treat node_modules idle for N days as stale. Default 60
--fast Skip the whole-home node_modules scan, which is the slow part
--version Print the version
--help Print usage
If macOS complains that the file came from an unidentified developer, clear the quarantine flag once:
xattr -d com.apple.quarantine mac-cleanup.shTerminal does not have access to Desktop, Documents and Downloads by default. Without it the script still runs correctly, but a few locations measure smaller than they really are, so the totals become a floor rather than a ceiling. The script detects this and says so at the top of the report.
To grant it: System Settings, Privacy and Security, Full Disk Access, add Terminal, then quit and reopen Terminal.
Every target is a cache, a log, or a downloaded artefact that the owning tool re-creates on demand. Nothing containing original work is ever a target.
The script refuses to run as root, because under sudo it would act on the root account's home folder rather than yours, and none of these caches need administrator rights. Every path is verified to sit inside your home folder before it is listed, so even if a tool reports a cache location elsewhere on the system it is ignored. Deletions empty a directory rather than removing it, which keeps permissions and ownership intact. Anything requiring judgement goes in the review list instead of the delete list.
Docker cleanup is limited to docker system prune -f, which removes dangling images and stopped containers. Volumes are never touched, since those hold database contents.
Reclaimable locations, largest first:
SIZE WHAT TIER
------------------------------------------------------------
6.5 GB Xcode iOS DeviceSupport dev
5.8 GB Gradle caches dev
4.2 GB User caches safe
2.8 GB npm cache dev
740 MB Xcode previews cache dev
380 MB CoreDevice support dev
------------------------------------------------------------
Subtotal: 20.4 GB
Desktop app caches, largest first:
SIZE APP
------------------------------------------------------------
4.5 GB Microsoft/Teams
1.5 GB Notion
1.2 GB Code
------------------------------------------------------------
Subtotal: 7.2 GB
Built by Titus Batson. More work at batsonlabs.com.
If this script freed up a useful amount of space, you can say thanks at buymeacoffee.com/titusbatson.
MIT.