Agent Skillsnubjs/nub › disk-reduction

disk-reduction

GitHub

用于清理 Rust 构建残留以释放磁盘空间。处理工作树私有 target 目录和孤儿共享目标桶,提供安全清理脚本及 df/du 差异说明,解决 ENOSPC 问题。

.claude/skills/disk-reduction/SKILL.md nubjs/nub

Trigger Scenarios

磁盘空间不足或即将耗尽 Rust 构建失败因无空间 定期清理构建残留

Install

npx skills add nubjs/nub --skill disk-reduction -g -y
More Options

Non-standard path

npx skills add https://github.com/nubjs/nub/tree/main/.claude/skills/disk-reduction -g -y

Use without installing

npx skills use nubjs/nub@disk-reduction

指定 Agent (Claude Code)

npx skills add nubjs/nub --skill disk-reduction -a claude-code -g -y

安装 repo 全部 skill

npx skills add nubjs/nub --all -g -y

预览 repo 内 skill

npx skills add nubjs/nub --list

SKILL.md

Frontmatter
{
    "name": "disk-reduction",
    "description": "Reclaim disk on the maintainer's Mac when the volume is full or filling — ENOSPC, \"no space left on device\", a failed build or agent harness, or a routine sweep of Rust build residue. Invoke whenever free space is the problem. Covers the two families that actually hold the space (worktree-private `target\/` dirs and orphaned content-hashed shared-target buckets under `~\/.cache\/nub`), the safety gates that keep a live bucket and the installed `nub-dev` binary alive, and why `du` and `df` disagree by tens of GB on APFS. For CPU load, orphaned processes, or a build hung on a target lock, use `cpu-reduction`; for not creating the residue, `rust-build-hygiene`."
}

Disk reduction — reclaim space from Rust build residue

The dev Mac has a 1.8 TiB data volume that fills with Rust build output. Measured 2026-08-14: the volume hit 340 MiB free — every agent tool call failed ENOSPC before it could run — and a sweep took it to 189 GiB without deleting one line of source.

Judge every reclaim by df, never du. scripts/rust-build.sh CoW-clones buckets with cp -c, so APFS bills the same physical blocks to every referencing path. Summed du sizes overstate what you get back — measured, a set of buckets du called 135 G returned 73 GiB of df. A du -sh over ~/.cache/nub also takes minutes; df is instant.

df -h /System/Volumes/Data

Where the space actually is

Ranked by what a sweep returns, with the measured 2026-08-14 figures:

Family Reclaimed Tool
Worktree-private target/ dirs 101 GiB cpu-reduction's clean-worktree-targets.py
Orphaned shared-target-<key> buckets 87 GiB clean-shared-buckets.py (this skill)
Merged worktree checkouts ~1 GiB git worktree remove

Build output is the whole story; checkouts are not. A worktree checkout is CoW-cloned source — removing four of them returned about a gigabyte. Never delete a checkout to recover space.

1. Worktree-private targets — the biggest single win

A worktree that diverges a depended-on crate builds into a private $root/target, and these are what dominate. The cleaner lives in the sibling skill; do not duplicate it here.

python3 .claude/skills/cpu-reduction/scripts/clean-worktree-targets.py           # audit
python3 .claude/skills/cpu-reduction/scripts/clean-worktree-targets.py --apply

It protects the entire target set of any worktree with uncommitted work, refuses while a Rust build runs, and never touches a shared bucket.

2. Orphaned shared buckets — the win nothing else covers

python3 .claude/skills/disk-reduction/scripts/clean-shared-buckets.py           # audit
python3 .claude/skills/disk-reduction/scripts/clean-shared-buckets.py --apply

Why buckets orphan so fast. A bucket is ~/.cache/nub/shared-target-<key>, where <key> hashes the content of the depended-on crates (vendor/aube and crates, excluding the leaves nub-cli/nub-native/nub-phantom). Two facts compound:

  • Only a NON-diverged worktree resolves to a bucket at all. The moment a branch touches a depended-on crate, rust-build.sh sends it to a private $root/target instead — so most feature branches reference no bucket, and a bucket's referrers are only ever the worktrees sitting at some exact content state.
  • main advancing moves the key. Every merge that touches vendor/aube or a non-leaf crate strands the previous bucket.

rust-build.sh has its own GC, but it only retires a bucket after 14 days of untouched mtime, so a fortnight of churn accumulates first. On 2026-08-14, 13 of 15 buckets proved orphaned — 12 in the first pass, holding ~135 G by du and returning 73 GiB of df, plus a 15 G bucket that orphaned the moment its last worktree was removed. Re-run the audit after removing any worktree.

What the script never deletes:

  • the bucket owning the installed nub-dev / nubx-dev symlink — deleting it breaks the dev binary with no error until you run it;
  • the newest bucket, which newest_bucket() CoW-clones as the seed for every isolated worktree — lose it and the next fresh worktree pays a cold build;
  • anything, while a cargo/rustc/rust-lld process runs or a .seeding clone is in flight — deleting a bucket mid-build truncates an rlib.

The built-in positive control. The key computation is duplicated from rust-build.sh and can drift, so the script recomputes the main tree's key and compares it against what scripts/rust-build.sh --print-target actually resolves. --apply refuses unless that control passes. Read the control: line before trusting any verdict.

3. Merged worktrees — hygiene, not reclaim

Remove only worktrees that are clean AND merged. git worktree remove refuses a dirty tree; never force it.

gh pr list --state merged --limit 400 --json headRefName --jq '.[].headRefName' | sort -u > /tmp/merged.txt
git worktree list --porcelain | awk '/^worktree /{print $2}'   # then check each: dirty? merged?
git worktree remove ~/.cache/nub/worktrees/<slug> && git worktree prune

git branch --merged origin/main misses most of them — this repo squash-merges, which leaves no ancestry link, so a squash-merged branch reports unmerged. Ask GitHub for merged PR head refs and check ancestry only as a fallback for detached-HEAD worktrees.

Removing a clean worktree is lossless — the branch and its commits stay in the repo — but it returns almost nothing, so do it for tidiness and stop there.

4. Gotchas that each cost a round trip

  • This shell is zsh; rust-build.sh is #!/bin/sh. zsh does not word-split an unquoted $var, so lifting the $leaves pathspec idiom into an interactive shell collapses three :(exclude) pathspecs into one bogus one and yields a different hash. Every bucket then looks orphaned, including the live one. Write the pathspecs as separate literal arguments, or drive git from Python where argv is explicit.
  • Never map buckets by running --print-target in every worktree. It is not a read: it mkdirs and touches the target (the liveness signal), CoW-seeds a private target for an isolated worktree — recreating the multi-GB dirs you just deleted — and fires the 14-day GC. Use it once, in the main tree, as the control.
  • You cannot fake a cargo process by copying a system binary. cp /bin/sleep /tmp/cargo breaks the code signature, and arm64 macOS kills the copy on exec — so a "is a build running?" negative control silently tests nothing and reports the guard as broken. Test the parser against synthetic ps output instead.
  • grep -E 'lld' matches installd. macOS runs installd, system_installd and appinstalld continuously, so a loose linker grep reports builds on a completely idle machine. Match the executable basename exactly.
  • Read ps -Ao comm=, not command=, when hunting build processes. An argv listing includes your own command line, so any check that mentions cargo matches itself and blocks forever.

5. Verify

df -h /System/Volumes/Data                       # the only number that counts
nub-dev --version                                # proves the surviving bucket still owns the binary
ls -dt ~/.cache/nub/shared-target-* | head -1    # the seed for the next isolated worktree

A sweep that leaves nub-dev broken has not succeeded. Re-run make install-dev if the bucket it pointed at is gone.

Version History

  • b140840 Current 2026-08-16 20:09

Same Skill Collection

.agents/skills/linux-vm-test/SKILL.md
.agents/skills/windows-vm-test/SKILL.md
.claude/skills/audit-thread/SKILL.md
.claude/skills/download-stats/SKILL.md
.claude/skills/git-archaeology/SKILL.md
.claude/skills/linux-vm-test/SKILL.md
.claude/skills/md-toc/SKILL.md
.claude/skills/plan-thread/SKILL.md
.claude/skills/pm-perf-tracing/SKILL.md
.claude/skills/research-thread/SKILL.md
.claude/skills/soak/SKILL.md
.claude/skills/todo/SKILL.md
.claude/skills/type-declarations/SKILL.md
.claude/skills/windows-vm-test/SKILL.md
skills/nub/SKILL.md
.agents/skills/agent-browser/SKILL.md
.claude/skills/ad-hoc-test/SKILL.md
.claude/skills/address-issue/SKILL.md
.claude/skills/agent-browser/SKILL.md
.claude/skills/aube-bump/SKILL.md
.claude/skills/aube-sync/SKILL.md
.claude/skills/ci-adhoc-test/SKILL.md
.claude/skills/ci-triage/SKILL.md
.claude/skills/ci-watch/SKILL.md
.claude/skills/cpu-reduction/SKILL.md
.claude/skills/dev-loop/SKILL.md
.claude/skills/dynamic-churn/SKILL.md
.claude/skills/epic/SKILL.md
.claude/skills/impact-analysis/SKILL.md
.claude/skills/implementation-thread/SKILL.md
.claude/skills/lat-md/SKILL.md
.claude/skills/probe-platforms/SKILL.md
.claude/skills/prose-writing/SKILL.md
.claude/skills/release/SKILL.md
.claude/skills/remote-build/SKILL.md
.claude/skills/rust-build-hygiene/SKILL.md
.claude/skills/rust-build/SKILL.md
.claude/skills/visual-review/SKILL.md
.claude/skills/worktree/SKILL.md

Metadata

Files
0
Version
b140840
Hash
03b58d9c
Indexed
2026-08-16 20:09

Главная - Вики-сайт
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-08-17 02:17
浙ICP备14020137号-1 $Гость$