It's the morning of April 29, 2026. I've been chasing disk hogs with dumap for a while wondering where the hell my disk space keeps disappearing. Docker volumes? node_modules? Old Xcode runtimes?
But every time I checked back a few weeks later, I was short again. Something was leaking, and I had a hunch I knew where.
The problem
Cargo's target/ directories.
Years of SaaS work, projects, prototypes, CLI tools, every random cargo new I'd ever run. Each one with its own multi-gigabyte cache (deps/, build/, incremental/, .fingerprint/), never cleaned up. Spread across ~/www and ~/labs — too scattered for any single cargo clean to catch.
The naive fix is to delete every target/ whole. But then I'd lose yesterday's release/ build that I might still want to run (like some MCP that I wrote and use locally). I wanted the cache gone, not the binaries.
What about cargo clean? It's too aggressive, cargo clean wipes a project entirely.
That was a hard No for me. So I sat down and wrote it.
The solution
A Cargo subcommand that walks any directory, finds every target/ it can prove is one (CACHEDIR.TAG or .rustc_info.json, the markers Cargo itself drops), and removes only the cache subtrees. The compiled binaries (release/myapp, debug/myapp, examples, dylibs, rlibs, wasm) stay put.
First run on my laptop:
done: 127 target dir(s), 4199 path(s) removed, 773.17 GiB freed
773 GiB. I re-read that line three times. Almost a full terabyte across 127 target dirs, accumulated without me ever noticing. That's a LOT for a 2 To drive!!
If I had this much sitting on my disk, I can't be the only one. So I open-sourced it the same morning.
How it works
A single src/main.rs. No dependencies beyond std. ~600 lines.
The detection rule is the only thing that really matters: a directory is treated as a Cargo target only if it contains CACHEDIR.TAG or .rustc_info.json. A folder named target/ that isn't actually one (some random project's data dir, whatever) is left alone.
Inside each profile (debug/, release/, custom):
| Kept | Removed |
|---|---|
Top-level executables (no ext, .exe) |
deps/, build/, incremental/, .fingerprint/ |
Dynamic libs (.so, .dylib, .dll) |
Top-level .d, .rmeta, .pdb, .o, .obj |
Static libs (.a, .lib, .rlib) |
doc/, package/, tmp/ |
WebAssembly (.wasm) |
sqlx-tmp/, cargo-timings/ |
examples/ final binaries |
examples/{deps,build,incremental,.fingerprint}/ |
Target-triple wrappers (x86_64-apple-darwin/, aarch64-unknown-linux-gnu/, …) get descended into and each profile inside is cleaned the same way.
Try it
cargo install cargo-clean-targets
# preview first
cargo clean-targets --dry ~/code
# do it
cargo clean-targets ~/code
Or via Homebrew:
brew tap FGRibreau/tap
brew install cargo-clean-targets
Or cargo binstall for a prebuilt binary (no compile):
cargo binstall cargo-clean-targets
The dry-run output tells you exactly what it's about to do:
target: /Users/me/code/myproj/target
would rm dir /Users/me/code/myproj/target/release/.fingerprint (12.04 MiB)
would rm dir /Users/me/code/myproj/target/release/incremental (48.21 MiB)
would rm dir /Users/me/code/myproj/target/release/deps (612.80 MiB)
would rm dir /Users/me/code/myproj/target/release/build (4.10 MiB)
done: 1 target dir(s), 5 path(s) removed, 677.82 MiB freed (dry)
What's next
- An age filter (only clean targets untouched for >N days), to preserve hot projects
- A
--keep-docflag for folks who actually usecargo docoutput - A reporting mode that shows per-project savings before touching anything
Open to other ideas. The whole tool is one file. Easy to hack on.
Run it tonight
Seriously. Run it on your dev machine with --dry and tell me what number comes back. I'm curious whether 773 GiB is an outlier or whether half of us are walking around with terabytes of dead Rust caches.
Star it if you find it useful. Report bugs. Send PRs.