Mac mini disk — root cause and the fix that sticks

Why the 460 GB drive keeps hitting the 20 GB floor despite five cleanup rounds, what was measured on 2026-09-28, and the structural change. For Fede and Gera.

2026-09-28 · Measured by session 004 at Fede's ask ("look urgently… stopping cloud sessions") · Local fix, no repo change (pnpm ask withdrawn)

The cleanups never stuck because they reaped finished work, and the space is held by unfinished work plus two hiding places no reaper can see. Result tonight: 17 → 64 GB free, without touching anything a process was using. The durable fix is one shared package store (pnpm), a cap on live checkouts, and checkouts only where the reapers look.

Details

Where the 445 GB was (whole-volume scan, du -xd1 /System/Volumes/Data, 2026-09-28 after the first strip)

AreaGBWhat it is
Home folder267175 checkouts of propflowai across ~/.claude (140), ~/agents (17), ~/repos (14), ~/worktrees (4); transcripts 16; Library 40 (14 Application Support, 5.7 iPhone symbols, 3.7 Messages); Downloads 9
/private6746 in session scratchpads (one session: 11 full clones of the repo, 40 GB, 1.75M files); 18 in per-user temp (node compile cache 7.2, tsx cache 6.6)
Applications + System + Library + opt45fixed

Folder sizes lie here. npm installs on APFS are clones that share blocks: 66 worktrees' node_modules reported 137 GB and freed 29 GB when removed (~0.45 GB unique each). Only a whole-volume scan compared with df is trustworthy for attribution; every earlier audit used per-folder sizes.

Why five rounds of cleanup did not stick (9/12 → 9/28)

Done tonight (2026-09-28)

ActionFreed
Stripped node_modules/.next from 66 checkouts idle 12 h with no process inside (they reinstall on use)29 GB
Stripped 3 finished scratch clones (#13166 closed, #13357 and #13333 merged), no process inside~7 GB
Deleted node compile cache + tsx cache (rebuild on use)~14 GB
Spotlight exclusions for ~/.claude, ~/agents, /private/tmp/claude-501 (Fede ran it); load 38 → 6—
Hooks: scratch-clone-guard.sh (no clone / worktree add into /tmp or scratchpad); grep-scope-guard.sh (no whole-home transcript greps)—

The fix that sticks (local, installed 2026-09-28 late; no repo change)

Fede: "I want a solution with our current setup." We stay on npm. macOS (APFS) already shares identical package files between checkouts, so a checkout really costs about 0.45 GB, not 2.9 GB; the pnpm recommendation is withdrawn. What is now running on the Mac:

  1. Deps are stripped by the hour, not the day. The 15-minute reaper removes node_modules and build output from any checkout nobody has touched for 6 hours when free space is under 60 GB (24 hours otherwise). The work stays; deps come back with one install from the kept npm cache. Nothing with a live process inside is ever touched.
  2. Every hiding place is scanned. The reaper now also looks at unregistered copies of the repo under ~/agents, ~/repos, ~/worktrees, ~/.claude and every session scratch folder under /private/tmp/claude-501. Those get the same strip rule; they are never deleted (they may hold unpushed work).
  3. Checkouts only under ~/agents as registered worktrees. A hook refuses new repo copies in temp or scratch space.
  4. The npm cache is kept. The nightly janitor no longer deletes it, so a stripped checkout reinstalls in about two minutes instead of re-downloading everything.
  5. Idle worktrees park themselves. When free space is under 80 GB, or more than 40 worktrees are registered, any worktree idle 48 hours with no process inside is parked: its edits are committed, its commits saved as a small git bundle in a backed-up folder, and the folder removed. Restorable by name. Nothing is pushed, so no CI runs and the pre-push check is never bypassed.
  6. Alert at 40 GB with attribution. When a reaper pass ends under 40 GB free it logs where the space is (whole-volume, the only honest view) and posts one line to #updates-fede, at most once per 12 hours.

Result 2026-09-28: 17 GB free at 18:00 → 154 GB at 21:40; registered worktrees 109 → 24. Fede at 21:25: "just clean up the idle worktrees." Every worktree idle 24 h with no process inside was parked, then removed: 36 pushed to GitHub as wip/<name>-20260928 branches (no CI runs on those), 47 archived as git bundles in ~/.claude/worktree-parked/ (8 MB total, covered by the S3 backup; restore with git fetch <bundle> HEAD) because the pre-push check refuses branches far behind main and that check is never bypassed. 22 fresh and 4 in-use worktrees untouched. Ledger: ~/.claude/logs/worktree-park-2026-09-28.tsv. Spotlight: exclusions for the five code folders applied by Fede (needed the mdutil off/on toggle to take), indexing itself stays on. A 5-minute machine sampler (machine-watch.sh, launchd) and 4-hourly reviews run for 24 h from 21:30. Still Fede's call (uncommitted or unpushed work, or personal files): 83 checkouts with uncommitted changes and 20 with unpushed commits, transcripts kept 60 days locally (~8 GB if cut to 30; S3 keeps them), Downloads 8.8 GB, iOS symbol cache 5.7 GB, Messages 3.7 GB, Whisper model cache 3.4 GB.

Not verified

24-hour machine watch (Sep 28 21:23 → Sep 29 21:23)

Verdict: the Mac was not a bottleneck for agentic work, except for one self-inflicted stretch. From 21:30 to about 08:00 Spotlight (the Mac's search indexer) rebuilt its index after it was switched off and on, and it ate 2 to 3 cores in bursts. That pushed load past 20 many times and the load hook refused some builders' tests. After 08:11 it was quiet. The only bigger load spike (152 at 13:31) was two full test suites running at once, which is real work. Memory, swap and CPU throttling were never a problem. Disk was fine all day, but it fell fast after the window closed (see below).

MeasureResult (288 samples, every 5 min)
Load (12 cores)median 10.5, 95th percentile 30.5, max 152 (13:31)
Samples with load over 2078 of 288. Search indexing led 41 of them, real work (node, git, tsgo) led 37. Before 09:00 nearly all were Spotlight-led; the biggest were 13:26 to 13:36 (110, 152, 133) and 22:30 on Sep 28 (61, a git burst).
Free disklow 110 GB (21:07 to 21:22 Sep 29), high 189 GB (about noon)
Largest one-hour disk drop23 GB (15:17 to 15:57, new lane checkouts and installs)
Swap used, memory under 20% free, CPU limit setnone; memory free never below 77%
Non-agentic program on top at over 50% CPU84 samples: Spotlight 70, photo analysis 10, Zoom 3, malware scan 1
Of those, lasting 2+ samples in a rowYes: 15 stretches. Longest 13 samples (23:35 to 00:35), then 8 (04:11 to 04:46) and 6 (three times). All Spotlight, except photo analysis 08:36 to 08:56 (5).

Bottlenecks found

Fixes applied

Still needs Fede

Recommended standing changes

Oct 1: what every cleanup measure actually did (judged from the logs)

Free space fell from 189 GB (Sep 29, noon) to 19 GB (Oct 1, 08:53), about 115 GB a day, with every cleanup step switched on. Three of those steps had been quietly doing nothing for days. Their code comments said they worked; their logs said otherwise. All three are fixed today, and free space is back to 41 GB. The target is to stay above 100 GB.

What we triedDid it work?What happened
Manual cleanups (Sep 12, 18, 28)OnceEach freed 60–150 GB, then filled back up within days.
Nightly cleanupBarelyFrees at most 10 GB a night. We lose up to 115 GB a day.
Weekly cleanupNever ranIt couldn't find the GitHub tool. Fixed today.
Cleanup every 15 minutesPartlySkipped 1,933 finished copies because it thought they had unsaved work. Fixed today; first run freed 15 GB.
Removing copies idle 1–2 daysBarelyIts own check kept resetting the idle timer. Fixed today; 30 removed since.
Clearing code cachesNeverIt waited 2 days; the caches were never that old. Now 12 hours; freed 7 GB.
Sharing installed packagesYes, for speedMade new copies fast. Saved little disk; the copies were already sharing space.
Slack alertToo lateFired at 38 GB. Now fires under 100 GB.

Three patterns behind the misses

Changed today (local, on the Mac mini; no repo change)

Still open

Sources: ~/.claude/logs/ (worktree-reap-fast, worktree-park.tsv, disk-janitor, worktree-reap), repo PRs #12019, #12062, #12120, #13739 and #14115, and ADR-0037 (agentflow-relay). Measured 2026-10-01 by session 006.

PropFlow Docs