disk-cleanup
Skill1 starsChanged 18 days agoMITView on GitHub
Use when the user wants to free up disk space or find what is eating storage — phrases like "clean up my disk", "running out of space", "what is using all my storage", "clear caches", "find big files". Detects the host OS, scans every readable volume, and presents one table ranked biggest-to-smallest covering directories, large individual files, and applications. Removes nothing without explicit per-item approval. Do NOT use for cleaning up code (use a code-cleanup tool) or for freeing context/token budget (see context-audit).
20 other projects use a version of this.
Adopted by
Most recently taken up by
- addisonk/skillstheir own version, first seen Nov 8, 2026
- DaraBoth/skilljmskilltheir own version, first seen Nov 8, 2026
- jars-demo/jars-skillstheir own version, first seen Nov 1, 2026
- huihuisang/ajigu-skillstheir own version, first seen Nov 1, 2026
- pabloleonalcaide/development-skillstheir own version, first seen Nov 1, 2026
- TsinHzl/claude-code-configtheir own version, first seen Nov 1, 2026
Used with
Projects with it also use api-design, tdd and 1 more.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
MicrosoftSkillapi-design
Guide for designing and documenting RESTful APIs. Use when asked to design an API, create endpoints, or document an API.
Also in 3 projects that use this
GoogleSkilltdd
Test-driven development. Use when the user wants to build features or fix bugs test-first, mentions "red-green-refactor", or wants integration tests.
Also in 4 projects that use this
MicrosoftSkillrelease
Use ONLY when the user explicitly asks to cut/publish a Huabu desktop release or tag a version. Performs an outward action — bumps the version in apps/desktop/package.json, commits + annotated-tags vX.Y.Z, and pushes the branch + tag. That tag push auto-builds the macOS/Windows installers and publishes a GitHub Release. Provides `status`, `bump`, `tag-push`, and a one-command `run` (bump → tag-push).
Also in 3 projects that use this
Other versions20 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 20
- glebis/claude-skills★ 410same name
- mtarcure/claude-vibe-squad★ 220same name
- MakiDevelop/claude-skills★ 3same name
- vndee/engineering-skills★ 3same name
- DaraBoth/skilljmskill★ 2same name
- TsinHzl/claude-code-config★ 2same name
- addisonk/skills★ 1same name
- foogunlana/skills★ 1same name
- galonge/free-claude-skills★ 1same name
- pabloleonalcaide/development-skills★ 1same name
10 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository15 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- context-auditSkill
- daily-reportSkill
- end-of-daySkill
- finish-ticketSkill
- gmail-organizeSkill
- gov-job-finderSkill
- rebuild-integrationSkill
- resume-ticketSkill
- save-conversationSkill
- side-noteSkill
- start-of-daySkill
- start-ticketSkill
- ticket-statusSkill
- transcribe-videoSkill
The file
We split this skill into the rules it gives an agent
3 rules. 0 are common, 0 are distinctive, and 3 are very specific to this project.
0
Common
Most of the 13 edited versions we read keep these.
0
Distinctive
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 129
Use the METHOD column classify.py already printed next to each row in step 2 — never re-derive the method from the manifest, the tier, or …
- Very specific: line 144
One invocation per item for
trash/rm: Run it once per approved path, even when several trash rows were approved together in one grouped … - Very specific: line 161
Recognise the TCC signature specifically: When the error contains NSCocoaErrorDomain Code=513, afpAccessDenied, Operation not permitted, or …
“Common” is measured: kept by at least 6 in 10 of the 13 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Disk Cleanup
Overview
Scans every readable volume, ranks every consumer of storage in one table, and reclaims space only from vetted targets the user approves item by item.
The scan is wide; the proposal set is narrow. scan.py measures everything readable —
including paths that must never be deleted — so the user sees the full picture. Only two
kinds of row are ever proposed: the 18 entries in the macOS manifest, and a project
artifact (node_modules, target, .venv, venv) that passes the ownership test in
Notes below. Everything else renders review only.
The gate
This is a contract, not a guideline:
- Nothing is deleted without the user's explicit approval in this session, naming the items being removed. A grouped approval covers only the list just shown — it is not blanket permission and never carries into another run.
scripts/scan.pyandscripts/classify.pycontain no deletion code, by construction.tests/test_safety.pyscans every file underscripts/for delete/move/rename primitives and fails the suite if one appears. Deletion happens only in step 5 below, only for items just approved.- Removal defaults to
/usr/bin/trash. A manifest entry's owncommandruns instead of trash where trashing would relocate bytes rather than free them (cheap-tier package caches:npm cache clean --force,pnpm store prune,brew cleanup) or where there is no path to trash at all (docker-images, whose manifestpathis the placeholder__command_only__docker—classify.build_rowsgives it a readable row and a real size instead, fromscan.py'sdocker_pass()). Acheap-tier entry with nocommandis reclaimed with permanentrminstead of trash, for the same reason — trashing a regenerable package-manager cache only relocates the bytes, it does not free them; step 5 says so before the user approves it. Every manifest command is itself gated bytest_safety.py: plain argv, a fixed tool allowlist, no destructive words. - Never escalate with sudo. Report a privileged region as unmeasured
(
unreadable_count) and hand the user the command to run themselves — never run it for them.
Steps
1. Scan
python3 "${CLAUDE_SKILL_DIR}/scripts/scan.py" --json > "${TMPDIR:-/tmp}/disk-scan.json"Roots come from choose_roots() filtering df -h, not a hardcoded list — on a
typical Mac that resolves to /System/Volumes/Data (the volume ~ and /Applications
firmlink onto) plus any mounted external volume. Default depth is 6: from a volume root rather
than $HOME, ~/Library/Caches/* sits 5 levels down and ~/.cache/* 4 — depth 3 cannot
reach either.
A cold scan can take several minutes (one measured run: ~8.5 minutes at depth 6 across two
roots). Tell the user before starting. Results cache for 24h, and a cached payload
missing a field the current schema expects is treated as stale automatically. --refresh
forces a fresh walk.
2. Classify and present
python3 "${CLAUDE_SKILL_DIR}/scripts/classify.py" "${TMPDIR:-/tmp}/disk-scan.json"One command produces the whole report — not a manual join. It opens with a scanned:
header naming when the data is from (the oldest root, if the two were cached at different
times) and a plain STALE - rerun with --refresh line when that is over 24h old — a
days-old JSON must never render as though it were current.
Below that is the ranked directories+files table (paths canonicalised back to the
firmlink form, /Users/..., so the table shows what the user recognises, never the
/System/Volumes/Data/... mount form). Every proposed/ask row is in it, always,
plus the biggest 25 review only rows — there can be tens of thousands of those, kept
only for orientation, never for decision, so the table ends with a line naming how many
were omitted and their total size instead of drowning the actionable rows in them.
%ROOT is each row's share of its own scan root, not a sum across both roots — a
root's own self-row correctly reads 100%. A METHOD column names exactly how step 5
would reclaim that row's bytes (trash, rm, a manifest command, or empty Trash
yourself); step 5 reads this column and nothing else.
The report continues with an APPLICATIONS section sorted by size with LAST USED in
place of AGE (a null last_used renders unknown, never "unused" — mdls cannot tell
never-launched from not-yet-indexed; always review only, since removing an app is
uninstalling software, not clearing junk); the regeneration cost for every ask row,
biggest first (step 4 reads this, not the manifest — this is also where a qualifying
project artifact's own regeneration note lives); and a footer with:
reclaimableandreview onlytotals — labelled as overlapping, e.g.review only: 281G (includes the reclaimable bytes above, which sit inside larger review-only directories), because every reclaimable row sits inside some larger review-only ancestor and the two numbers answer different questions, not additive ones.unreadable (need sudo)— bytes and entry count behind a permission wall, derived per root asdf's used total minus whatdu's own unprivileged walk could see, floored at zero. This is the real sudo gap; it is reported, never folded intoreclaimable.paths that vanished mid-scan— gone before the walk reached them, a different blind spot than the sudo gap and never conflated with it.- a
no Full Disk Accessnote, only whenscan.py'sfull_disk_accessprobe came back ungranted and at least oneproposed/askrow's path sits under~/Library— it names how many rows are affected and where to grant it. Silent otherwise, including when access is granted: this is a heads-up before approval, not a prediction of which row will fail — TCC overridesos.access, so nothing here can tell you that in advance.
3. Stop
Present the table and stop. Do not pre-select anything.
4. Collect approval
Use AskUserQuestion:
freeandcheaprows (actionproposed) may be offered as one grouped question, itemizing the list the user just read.costlyandprojectrows (actionask) are asked individually, never grouped — for each one's regeneration cost, read the blockclassify.pyalready printed in step 2, not the manifest JSON.review onlyandprotectedrows are never offered.- When a row's
METHODisrm, say so plainly before the user answers. That row will be permanently deleted, not moved to Trash — the approval question must state that in words (e.g. "this permanently deletes ~/.cache/convex, it will not go to Trash"), not leave the user to assume trash-by-default applies. A~/.Trashrow is never offered at all — see step 5.
5. Execute approved items only
Use the METHOD column classify.py already printed next to each row in step 2 —
never re-derive the method from the manifest, the tier, or anywhere else. Reopening the
manifest and prefix-matching paths by eye is exactly how a trash-worded approval could
end up running a native command instead; METHOD exists so that never happens.
METHOD column says | Do this |
a command (e.g. npm cache clean --force, docker system prune) | run that exact command |
rm | permanent rm on the path(s) — cheap-tier regenerable caches with no native command only; trashing them would relocate the bytes, not reclaim them. The user was already told this in step 4; do not soften it here. |
trash | /usr/bin/trash <path> — one item at a time, never batched. See "Run per item, verify every outcome" below. |
empty Trash yourself | run nothing. This is ~/.Trash — report its size and tell the user to empty it themselves. Trashing the Trash reclaims 0 bytes while still counting toward what the user was asked about, and emptying it is inherently permanent, so it is the user's own action, never this skill's. |
Run per item, verify every outcome — never report a success you did not check. A command returning is not the same as an item being reclaimed:
- One invocation per item for
trash/rm. Run it once per approved path, even when severaltrashrows were approved together in one grouped question in step 4. A manifestcommandruns once per its natural unit instead (brew cleanupcleans everything Homebrew has cached in a single call — there is no per-item form of that, and none is needed since it either runs or it doesn't as one thing). - Why per item, not batched:
/usr/bin/trash a b cexits non-zero if any ofa,b,cfailed to move — the exit code alone cannot say which one. A batch invocation can half-succeed silently. Invokingtrashonce per path is what makes each outcome attributable to the item that produced it. - Capture the exit code of every invocation, then confirm the path is actually gone
(
test -e, or equivalent) before calling it reclaimed — for a manifest command, confirm from its own output that it reclaimed something. A non-zero exit or a path that still exists afterward is a FAILURE for that item, full stop. Do not infer success merely because the command was issued. - On failure, report the tool's actual error text, not a paraphrase.
"Logs" couldn't be moved to the trash because you don't have permission to access ittells the user something real; "trash failed" does not. - Recognise the TCC signature specifically. When the error contains
NSCocoaErrorDomain Code=513,afpAccessDenied,Operation not permitted, ordon't have permission to access it, say plainly that macOS privacy protection (TCC) blocked the operation — not a bug in the skill, not a bad path — and that the fix is granting the terminal Full Disk Access in System Settings → Privacy & Security → Full Disk Access. The skill will not do this for the user; say that plainly too, then move on to the next item. - One failure never stops the run. Continue through the remaining approved items after one fails — a single TCC-protected path failing must not abort items that would have succeeded. Carry forward, per item, whether it succeeded or failed and why: step 6 reports every one of them, not only the aggregate.
docker system prune is gated exactly like every other command entry, no differently —
run it only if the docker-images row appeared in step 2's table (meaning docker_pass()
in scan.py could reach the daemon and found reclaimable bytes) and only after that row
was individually approved in step 4, same as any other costly entry. When Docker is
unavailable — not on PATH, or its daemon isn't running, which is common since Docker
Desktop is frequently closed — no row is offered; report that Docker was unavailable and
move on, the same as for any other unreachable tool. Never run docker system prune from
memory of a prior session's row: a grouped or individual approval never carries into
another run, and this entry is no exception.
Show the exact command before running it. Run a dry run first when the tool has one
(brew cleanup -n, docker system prune --dry-run). Paths handed to trash (or rm)
are always absolute, so a leading - in a filename is never ambiguous to either — do not
add a -- separator defensively: this build's trash has no such flag and instead treats
a literal -- as a file to move, exiting nonzero even when the real path was trashed
successfully (verified in tests/test_execution.sh).
6. Verify and report
Report per item first, aggregate second. A df delta alone cannot distinguish "trashed,
awaiting an empty Trash" from "blocked by TCC" from "freed outright" — those mean
different things to the user, and a shortfall in the total cannot answer which items it
came from:
- List every approved item with the outcome step 5 already determined for it: reclaimed (the command exited zero and the path was confirmed gone — or, for a manifest command, confirmed by its own output) or failed, with the reason. For a failure, give the tool's real error text, and name Full Disk Access as the cause when that's what step 5 recognised. An item moved to Trash counts as reclaimed here even though its bytes are not yet freed from disk — say so; see the Trash note below. Never upgrade a "ran without a visible error" into "reclaimed" here — only report what step 5 actually verified.
- Then re-run
df -hand report bytes actually reclaimed against predicted, in aggregate. Explain any shortfall honestly — the usual causes are one or more failed items from the per-item list above, Trash not yet emptied (bytes moved, not freed), a sparse file reporting differently than its allocated size, or a daemon that was down. If items went to Trash, say so and note that emptying it is a separate, user-initiated step; items reclaimed withrmhave no such gap — they are already fully freed.
Tiers
| Tier | Meaning | Approval |
free | regenerates itself at no cost | grouped |
cheap | re-downloads on demand, incremental | grouped |
costly | regenerable but expensive in time or bandwidth | individual |
project | rebuildable, but the user's own work product | individual |
review only | not in the manifest | never proposed |
Notes
~/.cache/whisperholds the model weights this repo'stranscribe-videoskill needs. Removing them forces a multi-GB re-download. Always surface that when offering it.- A
node_modules-shaped hit only qualifies as aprojectcandidate when it has a sibling manifest (package.json,Cargo.toml, ...), is 30+ days stale, and its project's git worktree is clean — an unknown git state disqualifies it, it does not default to allowed. A bare name match returns far more hits than that on a real machine, mostly editor extensions and MCP servers under~/.vscode/extensions,~/Library/Application Support/*, and package-manager tool caches;classify.is_protecteddenylists those regardless of age or git state. This test runs on everynode_modules,target,.venv, andvenvdirectoryscan.py'sproject_pass()finds on every scanned root, not only under$HOME— a qualifying hit is itemized as its ownaskrow (tierproject,METHODtrash) with a regeneration note in step 2's table, same as any otheraskrow; it is never grouped intofree/cheap's batch approval. A non-qualifying hit is classified exactly like any other path — most landreview only, tool-directory hits landprotected— and never appears twice: if the directory also has a row from the ordinary size scan, that row is what gets upgraded, not duplicated. - Linux and Windows manifests exist (auto-selected via
classify.detect_os) but are small, conservative starter sets, not vetted like macOS's 18 entries.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

