rust-build-hygiene
GitHub规范 Rust 构建启动方式,严禁后台剥离导致进程孤儿化及锁资源占用。提供前台、后台追踪及子代理等正确运行策略,结合脚本优化性能与缓存,确保构建随会话结束自动清理。
Trigger Scenarios
Install
npx skills add nubjs/nub --skill rust-build-hygiene -g -y
SKILL.md
Frontmatter
{
"name": "rust-build-hygiene",
"metadata": {
"internal": true
},
"description": "Best practices for spinning up nub Rust builds so they are PERFORMANT and CLEAN THEMSELVES UP — the prevention side of the recurring orphaned-build problem on the maintainer's dev host. Invoke (via the Skill tool) before launching any `cargo build`\/`test`\/`clippy` you might background or leave running, when setting up a build in a sub-agent, or when deciding how to wait on a long build. Encodes the ONE rule that stops the bleeding — never DETACH a build (setsid\/nohup\/`& disown` reparent it to PID 1, it outlives its launcher, holds the target-dir lock for 30+ min, and `TaskStop` does NOT reap it) — plus how to background correctly (harness-tracked), how to wait on a long build (a sub-agent that owns the wait, never a detached shell), one-target-per- concurrent-build, the fast profile + QoS clamp, and cleanup-on-done. For clearing residue that already accumulated, see `cpu-reduction`; for the target- dir sharing\/isolation decision, see `rust-build`; for the worktree loop, `dev-loop`."
}
rust-build-hygiene — launch builds that die with you and clean up after themselves
Orphaned Rust builds outlive their launcher, hold target-dir locks (stalling other builds 30+ min), burn cores, and leave tens of GB of stale target/ behind. Every instance traces to the same root cause: a build launched in a way that survives the process that started it. (cpu-reduction is the mop; this is "don't spill.")
The one rule: NEVER detach a build
A detached build reparents to PID 1 the moment its launcher exits, so it outlives the agent/session/turn, keeps holding the cargo target-dir lock, and TaskStop does NOT reap it (TaskStop kills the agent, not its background bash jobs). This is the most common orphan and the usual cause of Blocking waiting for file lock on artifact directory on the next build.
# BANNED — these orphan to PID 1 and survive TaskStop:
setsid cargo build ... &
nohup cargo build ... &
cargo build ... & disown
How to run a build correctly, by situation
| Situation | Do this | Why |
|---|---|---|
| Quick interactive build/test (< a few min) | Foreground Bash call (through scripts/rust-build.sh) |
Dies with the turn; the harness caps foreground at ~10 min |
| A build you want to keep working alongside | Bash with run_in_background: true |
Harness-TRACKED — reaped when the session ends, shows in the background-jobs list; NOT detached |
| A long build you will REST on until it finishes | Dispatch a sub-agent that runs the build in ITS OWN foreground and returns the result | The sub-agent's liveness is what the harness tracks. Never rest on a bare background shell |
| A long build in CI / on a VM | scripts/ci-watch.ts (the ci-watch skill) or a sub-agent owning the watch |
Own the wait in a tracked process, never a detached poll loop |
Never fake-wait on a build with a detached shell + a sleep/poll loop.
Performance — reuse the cache, clamp the QoS, cap the jobs
- Build through
scripts/rust-build.sh(drop-in forcargo). It picks the right target dir (shared by default, auto-isolates when a worktree diverges a depended-on crate) and applies a darwin QoS clamp (taskpolicy -c utility) plus a job cap on big hosts (CARGO_BUILD_JOBS = ncpu-4).make qos-globaladditionally clamps every rustc on the host to utility QoS. - Use the
fastprofile to iterate (--profile fast→target/fast/nub, ~5s incremental), neverrelease(itslto=thin+codegen-units=1re-LTOs the whole binary every change). - One target dir per CONCURRENTLY-building tree. Two builds on one target dir serialize on cargo's lock — that IS the contention. A serial multi-phase epic reuses ONE dedicated warm target across its phases; never point two concurrent builds at it. See
rust-build. - sccache does nothing here (measured 0% cross-worktree hit — it keys on the rustc command line, which embeds the absolute target path). A stable per-tree target dir is the whole answer.
Self-cleaning
- A worktree owns its target.
git worktree remove <path> --forcedrops the worktree;rm -rf <path>-targetdrops its private target dir. Do both when the work lands. The shared~/.cache/nub/shared-targetis intentionally left for the next worktree. - A sub-agent that built in an isolated target cleans it up on completion — unless a serial chain will reuse it (then hand the warm target forward explicitly). Say which in the dispatch prompt.
- Prune stale worktrees periodically.
git worktree list→ remove dead ones →git worktree prune. Theworktreeskill owns the lifecycle;cpu-reduction§2b has the disk-pressure sweep. - Never
cp -rthe repo to isolate a build — the tree carries multi-GBtarget//.repos//node_modules. Usegit worktree addorgit clone --depth 1 file://$PWD+ a privateCARGO_TARGET_DIR.
If it already orphaned
cpu-reduction §2: ps ... | grep -Ei 'rustc|cargo|lld', find the detached build holding the lock, pkill -f '<target-dir>' (artifacts persist = still warm), hand the contention-free target to ONE fresh foreground build.
Version History
- 46280a5 Current 2026-08-03 01:43
- f966b97 2026-07-31 00:05


