Reclaim disk space on WSL2 by purging dev caches and orphaned build artifacts, then guide the user through compacting the ext4.vhdx from Windows. Use when: (1) the user says 'dev-clean-wsl', 'clean wsl', 'wsl disk full', 'free disk space', 'reclaim space', 'purge caches', or 'disk almost full' on a WSL machine; (2) a build or tool fails with ENOSPC / no space left; (3) the user wants to shrink the WSL virtual disk. Covers cargo target/registry, pnpm/npm caches, node_modules, and the vhdx-compaction step that actually returns space to the Windows C: drive.
Reclaim disk space on WSL2 by purging dev caches and orphaned build artifacts, then guide the user through compacting the ext4.vhdx from Windows. Use when: (1) the user says 'dev-clean-wsl', 'clean wsl', 'wsl disk full', 'free disk space', 'reclaim space', 'purge caches', or 'disk almost full' on a WSL machine; (2) a build or tool fails with ENOSPC / no space left; (3) the user wants to shrink the WSL virtual disk. Covers cargo target/registry, pnpm/npm caches, node_modules, and the vhdx-compaction step that actually returns space to the Windows C: drive.
argument-hint
[code-root]
dev-clean-wsl
Free space on a WSL2 machine, then hand the user the one step they must run
themselves to make that space real.
The crux (why WSL is different)
On WSL2 the Linux filesystem is a single ext4.vhdx file that lives on the
Windows C: drive. Two consequences drive this whole skill:
/ looking healthy is not enough. The disk that actually fills up is
usually C:, not the Linux /. The scan reports both — read C: first.
Deleting files inside WSL does not shrink the vhdx. The vhdx only grows; freed
blocks stay allocated until the vhdx is compacted from Windows. So every purge
below is "banked" inside WSL and becomes real free space on C: only after the
compaction step. Always finish with that step — skipping it makes the cleanup
look like it did nothing from Windows' side.
wsl --shutdown (required for compaction) kills this session, so compaction is
always handed to the user as commands to run in a terminal — never run
it from inside WSL.
code-root is optional (defaults to $HOME/repos, else $HOME) — the directory
searched for node_modules and orphaned target/ dirs. The deep home scan takes
~1 minute. It deletes nothing and prints: / vs C: usage, the vhdx path + size,
top home consumers, and per-category reclaimable sizes.
Step 2 — Present findings and confirm
Turn the scan into a short table grouped by safety, then get the user's pick
(AskUserQuestion works well for multi-select). Do not delete large or ambiguous
things silently — these caches are recoverable but rebuilds/re-downloads cost time.
Safety tier, highest-value-and-safest first:
Tier
Item
Why safe
Free (zero cost)
Orphaned in-repo target/ when a global target-dir redirect is active
Nothing builds there anymore — pure dead weight (often the single biggest win)
Free (zero cost)
Orphaned pnpm store/vN (older than the active version)
pnpm store prune never touches old store versions; they are abandoned
Safe
cargo debug/incremental, release/incremental
Pure incremental cache; only slightly slows the next build
Safe
cargo registry/src + registry/cache
Re-fetched on demand (keep registry/index — refresh is slow)
Safe
pnpm store prune on the active store
Drops only unreferenced packages
Safe
npm cache clean --force (~/.npm)
Re-downloaded on demand
Costs a rebuild
active cargo targetdebug/deps (the big GB)
Only cargo clean / rm reclaims it; forces a full cold rebuild
Reinstallable
idle projects' node_modules
pnpm/npm install restores them; clear projects you are not actively on
Leave alone unless the user explicitly names them:
User data, not cache — e.g. ~/youtube-dl, ~/Downloads, media. Never auto-delete.
These are project-specific and may be wanted; do not treat a generic dist/ as cache.
Step 3 — Purge (only what the user confirmed)
Safety rules:
Relative-path deletes only.cd into the parent, then rm -rf ./name —
never rm -rf /absolute/path. This is a guardrail against a mistyped absolute path.
Check nothing is using a dir before clearing it. Before wiping a target/,
build dir, or CI-runner workspace, confirm no live build/process owns it
(ps -eo cmd | grep -iE 'cargo|Runner\.Worker'). Clearing a dir mid-build breaks it.
Confirm "orphaned" before claiming zero-cost. An in-repo target/ is orphaned
only if a global target-dir redirect (env CARGO_TARGET_DIR or
~/.cargo/config.toml [build] target-dir) points elsewhere AND its newest artifact
predates the switch. The scan prints both signals.
Per-category commands (adapt paths from the scan output):
# Orphaned in-repo target (zero rebuild cost — redirect is active elsewhere)cd /path/to/repo && rm -rf ./target
# Orphaned old pnpm store version (active is vN; vN-1 etc. are dead)cd"$(dirname "$(pnpm store path)")" && rm -rf ./v10 # example: active is v11# cargo incremental cache (safe; minor next-build slowdown)cd"$ACTIVE_TARGET/debug" && rm -rf ./incremental
# cargo registry (re-fetched; keep index)cd"$HOME/.cargo/registry" && rm -rf ./src ./cache
# pnpm + npm caches
pnpm store prune
npm cache clean --force
# idle project's node_modules (reinstallable)cd /path/to/idle-project && rm -rf ./node_modules
For the big active cargo debug/deps, only offer cargo clean / deleting the whole
target dir if the user accepts a full cold rebuild — call out the tradeoff explicitly.
After purging, re-run a quick df -h / and re-measure the touched dirs so the
reported total reflects reality.
Step 4 — Hand off the vhdx compaction (the step that frees C:)
This is the finale every run must end on. Pull the vhdx path from the scan output and
give the user a ready-to-paste block for a Windows PowerShell terminal (not WSL):
Set expectations honestly: compaction reclaims only the free blocks inside the
filesystem (i.e. roughly what was just purged), so the vhdx — and C: usage — stays
about as large as the real data still inside WSL. State the rough before/after C:
free numbers from the scan so the user knows what to expect.
If the scan could not find the vhdx, have the user locate it on Windows with
Get-ChildItem -Recurse $env:LOCALAPPDATA -Filter ext4.vhdx.
Bonus — stale Codex processes (EMFILE / inotify exhaustion)
Not a disk problem, but the same "WSL slowly rots" family: orphaned codex plugin
brokers pile up (~3 processes each) and each eats inotify instances; at
fs.inotify.max_user_instances=128 Vite starts failing with EMFILE. Sweep them:
node $HOME/.claude/scripts/codex-sweep.js
(A SessionStart hook runs this automatically once per 6h; the manual run is for
when EMFILE already struck. See /codex-sweep for details.)
Belt and braces — raise the ceiling so accumulation between sweeps can't bite:
Self-hosted CI runners sometimes show up as large dirs. Their _work/ is
ephemeral CI workspace (safe to clear when idle); fully removing a runner means
stopping + uninstalling its root-owned systemd service first
(sudo ./svc.sh stop && sudo ./svc.sh uninstall), which needs the user's sudo
password — hand them those commands rather than guessing.
This skill assumes WSL2. On a non-WSL host the scan still reports reclaimable
caches, but the vhdx/compaction step does not apply — the script flags this.