cargo-broom
cargo-broom finds Cargo build artifacts across one project or an entire directory
tree and removes regenerable data without turning a routine cleanup into a risky
filesystem operation.
The current 0.1.0 codebase is a safety-first alpha. Conservative target cleanup,
inspection, JSON reporting, and selective cache cleanup are available. Cargo
fingerprint pruning remains experimental and is disabled by default.
Why cargo-broom?
cargo clean works well for one known project. It does not answer which of dozens
of local projects occupy the most space, which targets have gone cold, or what a
scheduled cleanup would remove. cargo-broom adds recursive discovery, age and
size policies, dry runs, interactive selection, and machine-readable reports.
Quick start
Install from a checkout:
Inspect first. Neither command modifies files:
Run a confirmed cleanup after reviewing the dry run:
Calling cargo broom without --dry-run, --interactive, or --yes fails before
the project scan begins.
Safety guarantees
- Project cleanup requires
--interactiveor--yes; registry cleanup requires--yes. - Whole-target cleanup is limited to a standard local
<workspace>/target. - Shared targets, configured target overrides, symlinked targets, and targets outside the workspace are never removed wholesale.
- A fingerprint parser error skips fine cleanup for that project. It never falls back to deleting the complete target.
- Staleness is judged by
mtime, notatime.atimeis unreliable onnoatime/relatimemounts (common on CI runners and some macOS setups), where it is not updated on every read or only coarsely, so recently-scanned-but-unused artifacts look falsely "recent".mtimeis written on every actual Cargo build and does not have this problem. - A target directory currently locked by a running
cargoprocess is skipped entirely, at both cleanup levels, instead of racing the build. - Filesystem deletion errors are reported and produce a failing exit status.
- Fingerprint pruning requires the explicit
--experimental-fineopt-in. --trashmoves Level A targets to the OS trash/recycle bin instead of deleting them permanently.
All removed data is generated Cargo output. Deletion can still cause rebuilds, so
use --dry-run before enabling an automated job.
Cleanup modes
| Mode | Selection | What it removes |
|---|---|---|
| Default / Level A | Target is at least --keep-days old and, when supplied, at least --keep-size large |
Complete standard local target/ |
--coarse-only |
Same policy as Level A; fine operations are rejected | Complete standard local target/ |
--clean-incremental |
Targets not selected for Level A | target/*/incremental caches |
--clean-doc |
Targets not selected for Level A | Generated target/doc output |
--experimental-fine |
Targets not selected for Level A | Fingerprints and matching hashed artifacts selected by experimental age and duplicate heuristics |
--toolchains <LIST> |
Modifier for --experimental-fine |
Also prunes fingerprints built with a rustc other than the named toolchain(s) |
--installed |
Modifier for --experimental-fine |
Same, but keeps any currently rustup-installed toolchain instead of a specific list |
--fine-only |
Disables Level A | Only explicitly requested fine operations |
--trash |
Modifier for Level A, any policy above | Moves the target to the OS trash/recycle bin instead of deleting it permanently |
Examples:
# Conservative whole-target cleanup only
# Same, but recoverable: goes to the trash instead of a permanent delete
# Level A for cold projects; incremental caches and docs for the rest
# Review experimental fingerprint pruning
# Also prune fingerprints from toolchains rustup no longer has installed
# Never remove a complete target
--fine-only requires --experimental-fine, --clean-incremental, or
--clean-doc. --tests-only, --toolchains, and --installed require
--experimental-fine. Conflicting mode combinations are rejected by the CLI.
--experimental-fine validates each fingerprint entry against its own JSON file
(the rustc hash Cargo records there) rather than trusting the .fingerprint/
directory naming pattern alone; an entry with no parseable fingerprint JSON marks
the whole project unsupported for fine cleanup instead of guessing (see Safety
guarantees above).
Commands
Inspect and diagnose
inspectlists discovered targets and their disk usage without applying cleanup policies.doctorreports target overrides, unusually large targets, and target lock findings.toolchainsreports installed rustup toolchains that are not referenced by arust-toolchainfile belowDIR. It does not uninstall anything.budgetreports total target disk usage across all discovered projects and, with--limit, flags when that total exceeds the budget together with the largest contributors. This is a whole-tree budget, unlike--keep-size, which is a per-project Level A threshold. Analysis only; it never deletes anything.
Clean one project
project uses the same policy and safety gates as a recursive run:
Clean the Cargo registry cache
The registry command scans Cargo.lock files below DIR and removes cached
.crate archives whose name and version are not referenced there. Projects outside
DIR are not considered. Cached archives can be downloaded again by Cargo, but a
dry run over the broadest relevant project root is recommended.
Interactive and JSON output
Use --interactive to select proposed targets before deletion:
Use JSON for scripts, reports, or scheduled dry runs:
Errors produce a non-zero exit status. JSON reports are written to stdout; process errors are written to stderr.
History tracking
--history (or history = true in broom.toml) is an opt-in, off-by-default flag
that appends each target's resulting size to a rolling 14-day JSON Lines log at
~/.local/state/cargo-broom/history.jsonl, one line per target per run. Entries
older than 14 days are pruned automatically; dry runs are never recorded, since they
do not reach the sizes they report.
Once a target has at least one prior entry, the report gains a History metric
comparing the current size against that target's oldest still-retained entry —
answering "is this growing back despite regular cleanup?" instead of only showing
the current run's numbers. The same data is available under history_trend in
--format json output for scripting.
Configuration
The first applicable configuration source is used in this order:
- the file passed through
--config; ./broom.toml;~/.config/cargo-broom/config.toml.
An explicit file is not merged with the local or global file. Scalar CLI values
take precedence; ignore and skip lists are combined. Invalid files and unknown
keys are errors instead of being silently ignored. ~ is expanded in root_path.
= "~/GitHub"
= 14
= 50
# Keep experimental pruning disabled for unattended runs.
= false
= false
= false
= false
= false
= false
= ["archived-repo"]
= ["node_modules"]
Confirmation is intentionally not configurable. An unattended destructive run
must include --yes in the command itself.
Scheduled cleanup
Start by logging dry-run output for several runs:
After reviewing the policy, an explicit conservative command is suitable for cron or launchd:
The included de.casoon.cargo-broom.plist is a machine-specific example. Adjust
its executable, root, and log paths before loading it.
CI build caches
cargo-broom targets a developer machine with many long-lived local checkouts,
cleaned up periodically by age and size. It is not a fit for a target/ directory
persisted across CI runs (e.g. via actions/cache): that cache grows for a
different reason — every dependency bump leaves behind fingerprints from the
previous Cargo.lock state — and calls for cache-key-based or content-addressed
invalidation instead of an age heuristic. For that case, prefer a tool built for it:
Swatinem/rust-cache— GitHub Action that keys the cache onCargo.lockplus toolchain version and prunes known-safe-to-drop paths (incremental/, final binaries) before saving. The default choice for GitHub Actions.sccachewith a remote backend (S3, GCS, or the GitHub Actions cache backend) — content-addressed compiler cache, so entries are looked up by input hash rather than file age; sidesteps staleness entirely.cargo-chef— for Docker-based pipelines, separates dependency compilation from application compilation into distinct, correctly invalidated Docker layers.- Registry-only caching — cache just
~/.cargo/registryand~/.cargo/gitand skiptarget/altogether. Simpler, and often enough when CI runners are fast and dependency compilation dominates build time.
Development
The project requires Rust 1.89 or newer (for std::fs::File::try_lock, used to detect
an in-progress build before cleaning its target directory).
CI runs formatting, Clippy, and tests on Linux, macOS, and Windows. The package uses
the published runemark dependency and does not require a sibling repository.
License
MIT
The rustc-version hashing in --toolchains/--installed (src/level_b.rs) is
ported from cargo-sweep (MIT), which
mirrors Cargo's own internal fingerprint hash.