Skip to content

Add temp-file and Cargo-unit cleaners, reclaiming without external tools - #7

Merged
UnbreakableMJ merged 1 commit into
mainfrom
feature/native-cleaners
Aug 6, 2026
Merged

Add temp-file and Cargo-unit cleaners, reclaiming without external tools#7
UnbreakableMJ merged 1 commit into
mainfrom
feature/native-cleaners

Conversation

@UnbreakableMJ

Copy link
Copy Markdown
Contributor

Vacuum could not cover the workflow it was built to replace. Reclaiming disk space still meant dropping to a shell for sudo rm -r /tmp/*, a system-wide nix-collect-garbage, a time-based journalctl --vacuum, and cargo-sweep.

Two new cleaners, both executing nothing

temp-files (new category) is the safe counterpart to sudo rm -r /tmp/*, which destroys other users' files and the live state of running processes. An entry under /tmp, /var/tmp, or $TMPDIR is offered only when it is not a symlink, is owned by the invoking user, has gone untouched for seven days, and is not session state (.X11-unix, systemd-private-*, .Trash-*, …). Root-owned leftovers stay out of scope and go to a printed sudo systemd-tmpfiles --clean.

cargo-prune removes dead build units from inside a Cargo target/, so a project still in use keeps its warm cache. It replaces a cargo-sweep shell-out that could never have worked here:

  • cargo-sweep is unmaintained (its README says so).
  • It needs cargo metadata, rustup toolchain list, and rustc -vV, none of which run on a Nix- or Guix-managed toolchain — cargo-sweep sweep --dry-run --installed errors out on the maintainer's own machine.
  • Its --time mode reads atime, its oldest open bug: under relatime Cargo does not bump atime on artifacts it reuses, so it deletes warm caches.

Instead: target dirs are found by CACHEDIR.TAG; units are grouped by the "rustc" hash Cargo writes into each fingerprint, and the group with the most recent activity is the toolchain in use; freshness comes from invoked.timestamp. The incremental cache — ~30% of a target dir's bytes and something cargo-sweep never reclaims — is offered separately. A profile whose .cargo-lock is held by a running build is skipped.

Package-manager GC also gains system-wide nix-collect-garbage -d --verbose, journalctl --vacuum-time=7d, and systemd-tmpfiles --clean, all as printed sudo lines.

Core contract changes, required rather than incidental

  • Cleaner::extra_roots/tmp is outside $HOME, so every temp candidate would have been refused with OUTSIDE_ROOTS. The widened set applies only when that cleaner is selected; denylist and symlink guards untouched.
  • Candidate::trash_ok — a path under /tmp trashes into /tmp/.Trash-$uid on the same filesystem and frees nothing. These purge instead, with the reason reported, never silently. --apply still required.
  • Target::Batch — one logical reclaim spanning many paths. The safety gate runs over every path before any is removed, so a batch is refused whole rather than half-applied.

Sizes now count a hardlinked inode once; Cargo hardlinks its artifacts, so naive summing over-reported this workspace by ~12% and would have claimed the same bytes twice.

Frontends group by category rather than by cleaner (a latent bug: a second cleaner in a category produced duplicate JSON groups and duplicate headings). --cleaner selects one where candidates deliberately overlap; --stale-days and a stale_days config key set the window.

Verification

The toolchain-hash correspondence was proven before anything depended on it: hashing the -vV text Cargo stores in .rustc_info.json reproduces the fingerprint value exactly — 9571511559510505644 for rustc 1.95.0 and 125055456796404090 for 1.97.1, matching every fingerprint in the respective target dirs.

End to end, a crate was built with 1.95.0 then 1.97.1 to create two real toolchain groups in one target dir. Pruning the stale group left the live group and the unhashed binary intact, and the rebuild reported Fresh itoa / Fresh e2e — zero recompilation.

Gate: cargo fmt --check, clippy --workspace --all-targets -D warnings, 82 tests, reuse lint, makeinfo with no warnings.

🤖 Generated with Claude Code

https://claude.ai/code/session_01FCJbPm4FWS99y9utbUe13z

Vacuum could not cover the workflow it was built to replace. Reclaiming disk
space still meant dropping to a shell for `sudo rm -r /tmp/*`, a system-wide
`nix-collect-garbage`, a time-based `journalctl --vacuum`, and cargo-sweep.

Two cleaners close that gap, both reading the filesystem directly and executing
nothing:

* `temp-files` (new category) offers an entry under /tmp, /var/tmp, or $TMPDIR
  only when it is not a symlink, is owned by the invoking user, has gone
  untouched for seven days, and is not live session state. Root-owned leftovers
  stay out of scope and are reclaimed through a printed
  `sudo systemd-tmpfiles --clean` instead.

* `cargo-prune` removes dead build units from inside a Cargo target/ directory,
  so a project still in use keeps its warm cache rather than paying for a full
  rebuild. Units are grouped by the compiler hash Cargo records in each
  fingerprint; the group with the most recent activity is the toolchain in use
  and the rest are leftovers. Freshness comes from the invoked.timestamp file
  Cargo writes for the purpose, never from access time, which relatime does not
  update when Cargo reuses an artifact. The incremental cache is offered
  separately, and a profile whose .cargo-lock is held by a running build is
  skipped entirely.

Package-manager GC also gains system-wide `nix-collect-garbage -d --verbose`,
`journalctl --vacuum-time=7d`, and `systemd-tmpfiles --clean`, all as printed
sudo lines; Vacuum still never escalates.

Three changes to the core contract were required rather than incidental:

* `Cleaner::extra_roots` lets a cleaner declare delete roots beyond the scan
  roots. /tmp is outside $HOME, so every temp candidate would otherwise be
  refused as outside the allowed roots. The widened set applies only when that
  cleaner is selected; the protected-prefix and symlink guards are untouched.

* `Candidate::trash_ok` marks candidates the trash cannot help. A path under
  /tmp trashes into /tmp/.Trash-$uid on the same filesystem and frees nothing,
  and a batch of hundreds of regenerable files would scatter as many trash
  entries. These purge instead, with the reason reported rather than applied
  silently. --apply is still required.

* `Target::Batch` carries the many scattered paths one logical reclaim covers.
  The safety gate runs over every path before any is removed, so a batch is
  refused whole rather than half-applied.

Sizes now count a hardlinked inode once. Cargo hardlinks its final artifacts
into place, so summing file lengths naively over-reported this workspace by
about 12% and would have claimed the same bytes twice.

Frontends group by category rather than by cleaner, so a category appears once
however many cleaners feed it, and --cleaner selects a single one where their
candidates deliberately overlap. --stale-days and a stale_days config key set
the staleness window.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FCJbPm4FWS99y9utbUe13z
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@UnbreakableMJ
UnbreakableMJ merged commit e968357 into main Aug 6, 2026
2 checks passed
@UnbreakableMJ
UnbreakableMJ deleted the feature/native-cleaners branch August 6, 2026 11:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant