
This walks you through macOS disk cleanup with a paranoid, safety-first approach that never auto-deletes anything. It leans heavily on Mole for analysis, uses tmux for interactive scans, and makes you confirm every deletion individually. The workflow is methodical: scan home directory (warn the user it takes 5-10 minutes), categorize what's actually useless versus useful cache, then hand you the exact commands to run yourself. It's opinionated about Docker prune commands (banned except builder cache) and explicitly designed for users in slow network environments where re-downloading gigabytes of npm or browser cache is painful. If you want aggressive one-click cleanup, look elsewhere. This is for when you need to reclaim space without breaking anything or spending hours re-downloading dependencies.
npx -y skills add daymade/claude-code-skills --skill macos-cleaner --agent claude-codeInstalls into .claude/skills of the current project.
Diagnose the actual source of disk pressure, reclaim only approved space, and prove that the cleanup did not damage user data or co-resident services.
Choose the narrowest path that can answer the request:
| User signal | Route |
|---|---|
Apple Content Caching, AssetCacheManagerUtil, CacheUsed, ActualCacheUsed, iCloud cache, or “Caching needs more space” | Read references/apple_content_caching.md completely before probing or proposing commands |
| Docker, OrbStack, images, containers, or volumes | Read references/docker_analysis.md; inspect every object and never use prune-family commands |
| Docker build cache | Measure with docker builder du; this skill reports it but does not delete it because Docker exposes category-wide prune controls rather than per-record intent |
| A named cache, directory, application, or service is already the suspect | Inspect that target first and read the matching semantics in references/cleanup_targets.md; do not start a home-directory or whole-disk scan |
| The source is genuinely unknown | Use the general analysis workflow below; Mole is optional, not the universal first step |
User-provided scope exclusions override every generic scan suggestion. Do not inspect personal directories, credentials, databases, application state, or unrelated services when the user excludes them.
rm -rf or glob deletion. For exact approved ordinary files, prefer Finder Trash. The bundled legacy helper permanently deletes and has only the limited guards documented below; never treat it as equivalent to Trash.references/safety_rules.md before any file deletion.Use this state machine for every cleanup:
Do not compress phases 2 and 3 into one message. A plan printed beside a cleanup command is not a confirmation gate.
At minimum, capture:
/bin/date "+%F %T %Z %z"
/usr/sbin/scutil --get ComputerName
/usr/sbin/scutil --get LocalHostName
/usr/bin/sw_vers
/bin/df -k /System/Volumes/Data
/bin/df -h /System/Volumes/Data
Use df -k for calculations and df -h for the human-readable report. Treat an extension, label, or old report as a hint until the live command confirms it.
Establish the success target before an unknown-source scan. Copy a user-supplied free-space or capacity target exactly. If the user supplied none, report the current values and ask for a target in GiB, capacity percentage, or both; do not invent one. A named-suspect diagnosis may continue without a cleanup target, but the ordered unknown-source scan cannot claim a stop condition until the target is explicit.
Keep this first phase read-only. Do not run scripts/cleanup_report.py yet: it creates a local state directory and snapshot file. Preserve the command output in the report instead. On a remote target, always run the direct df commands on that host; the local helper must not measure the controller Mac by mistake.
du on the exact data path only when permissions and user scope allow it.If the known suspect alone can meet the user's free-space target, do not scan unrelated personal or development directories “just in case.”
Run the smallest ordered sequence that can identify enough physical space to meet the target. Stop only when candidates with supported exact actions and defensible expected physical release can meet it. Raw allocation totals, logical cache sizes, shared Docker layers, Trash moves, and unverified “potential savings” do not satisfy the stop condition.
| Order / signal | Read-only action | Stop or continue |
|---|---|---|
| 1. Always | Capture identity and df -k/-h; inventory user exclusions | Stop on target mismatch |
2. Cache/log pressure, and ~/Library/Caches plus ~/Library/Logs are approved read scopes | uv run scripts/analyze_caches.py --user-only | Stop when measured candidates can meet the target |
| 3. Developer tools are present and the script's fixed scope is approved | uv run scripts/analyze_dev_env.py reads Docker/package managers plus existing ~/Projects, ~/workspace, ~/dev, ~/src, and ~/code roots | Route Docker/OrbStack findings to their dedicated reference; skip this helper when any fixed root is out of scope |
| 4. Uninstalled-app residue is plausible and its fixed roots are approved | uv run scripts/find_app_remnants.py reads /Applications, ~/Applications, and four documented ~/Library application-state roots | Treat every result as a candidate, never proof of abandonment; skip when that scope is not approved |
| 5. A content-bearing path is explicitly approved | uv run scripts/analyze_large_files.py --threshold 100MB --path "<approved-path>" | Do not substitute ~, Downloads, Documents, or the data-volume root when no path was approved |
| 6. Still unknown after bounded checks, and the user explicitly approves Mole's fixed broad scan roots | Read references/mole_integration.md and use mo analyze through a TTY | Mole cannot accept an arbitrary path scope; skip it when approval is narrower than its documented roots |
An <approved-path> is an exact path the user named or explicitly accepted after its scope was described. If none exists, skip large-file and duplicate-content scanning, state that this evidence branch was not authorized, and continue with non-content-bearing evidence. Do not install or upgrade Mole during a read-only phase unless the user separately authorizes that change.
Mole's analyzer scans a fixed set that includes the home directory, application data, system libraries, applications, and volumes. Navigation inside the results does not make the underlying scan path-scoped. If that broad read scope is not approved, do not run Mole; stop with the bounded evidence already collected or ask for the missing scan authorization in the plan.
For an explicitly approved duplicate-file investigation, read the “Optional duplicate files” section in references/cleanup_targets.md. It is read-only and never uses an automatic-delete option.
Read references/docker_analysis.md before reporting Docker savings. List every image, container, and volume individually; inspect references and database-like contents; use actual sparse-file allocation rather than apparent size. A resource reported as dangling is not proof that its data is worthless. Build-cache measurement is supported, but build-cache deletion is deliberately out of scope because the available Docker controls are prune-family operations.
Report observed values rather than inferred properties. Use references/report_templates.md for the long-form layout and include these fields for every proposed action:
| Field | Required content |
|---|---|
| Current state | Timestamp, target identity, disk used/free/capacity, and subsystem status |
| Evidence | The command and observed value; state whether the number is logical or physical |
| Exact command | The command that would change state, with the exact target or object ID |
| Change | What the command modifies or removes |
| Recoverability | Reversible command, Trash recovery, backup restore, or redownload-only |
| Expected release | Physical-space estimate with unit and assumptions |
| Service impact | User-visible effects and protected-service invariants |
| Postconditions | Values that must be true before the action is called successful |
Classify findings by consequence, not by how tempting the number is:
Do not call a cache “absolutely safe” merely because software can regenerate it. Regeneration time, bandwidth, authentication, and offline availability are real costs.
Immediately before the first state-changing command:
If the approved plan includes creation of a local before/after report artifact, capture the before snapshot now, after approval and before the first cleanup command:
uv run scripts/cleanup_report.py --snapshot before
This writes under ~/.macos-cleaner, so list it in the plan. It is optional and local-target only; direct df -k/-h readings remain the source of truth. Never run it on the controller Mac as a substitute for measuring a remote target.
Run one state-changing command at a time. Read the result before sending the next command. After each command, run the postcondition that can distinguish success from partial success.
For an exact ordinary file that is not user data, application state, a database, or a protected path, prefer a recoverable Finder Trash move after the user confirms the exact path:
/usr/bin/osascript \
-e 'on run argv' \
-e 'tell application "Finder" to delete (POSIX file (item 1 of argv))' \
-e 'end run' -- "<exact-path>"
Moving to Trash usually releases no physical space until Trash is emptied; state that in the plan. scripts/safe_delete.py is a legacy permanent-deletion helper with an interactive prompt and a limited system/credential denylist. It does not move to Trash, check every user-data root, detect open files, or independently prove reclaimed bytes. Use it only when the exact non-user-data target and irreversible deletion were explicitly approved:
uv run scripts/safe_delete.py <exact-path> [<exact-path> ...]
Do not use this helper for user documents, application-managed caches such as Apple Content Caching, databases, credentials, or any target whose role is uncertain.
Two narrow Finder-Trash branches remain available without weakening those exclusions:
references/cleanup_targets.md, explain the rebuild cost, and obtain explicit approval for that exact cache path.all, a glob, or a directory-level target. Verify the source paths moved to Trash, and state that physical space is not released until Trash is separately reviewed and emptied.Verification must cover all clauses of the approved plan:
df -k and df -h; calculate reclaimed space from before/after readings rather than from the deletion tool's claim.Never report “fixed” when only the command exit code is known. A successful cleanup requires both the intended state and the protected invariants.
For a local target whose before snapshot was captured by the helper, generate the comparison with:
uv run scripts/cleanup_report.py --snapshot after --compare
Load only the branch relevant to the current task:
references/apple_content_caching.md — Apple Content Caching diagnosis, unit interpretation, supported remote controls, confirmation plan, and post-cleanup verification.references/cleanup_targets.md — cache, log, application, developer, large-file, and Time Machine target semantics.references/docker_analysis.md — per-object Docker and OrbStack analysis, database-volume safeguards, and refill root-cause diagnosis.references/mole_integration.md — TTY workflow for interactive Mole analysis and preview.references/report_templates.md — long-form general and Docker report templates.references/safety_rules.md — blocked paths, confirmation, recovery, and file-deletion safety checks.scripts/analyze_caches.py — bounded cache inventory.scripts/find_app_remnants.py — application-remnant candidates; reads its fixed Applications and ~/Library roots, so require that scope first.scripts/analyze_large_files.py — large-file discovery inside an approved path.scripts/analyze_dev_env.py — Docker/package-manager inventory plus fixed common-project-root .git sizing; require that full read scope first.scripts/safe_delete.py — legacy guarded permanent deletion for exact approved non-user-data targets; it is not a Trash or recovery tool.scripts/cleanup_report.py — local-target before/after reporting for /System/Volumes/Data by default, with an explicit --volume override.