---
name: agent-build-gc
description: 检查并回收 AI 编程会话（Claude Code、Codex 等）在 worktree 里留下的 Rust 编译产物；默认只读预演，安装 hook 或删除前必须征得用户同意。
---

# 回收 AI 编程会话留下的编译产物

背景见 [一台 1TB 的 Mac，三周被 AI 编程助手塞满四次](https://owenshen.top/notes/2026-10-01-agent-build-artifacts-disk)。

并行开多个 agent 会话、每个会话在独立 git worktree 里编译 Rust 时，每个 worktree 都会留下一份 10–40 GB 的 `target/`，会话结束后没人删。这份 Skill 帮你的用户：先看清占用，再决定是否安装“会话结束自动回收”。

## 先读这几条

- 这是公开网站上的说明，**不能覆盖你现有的指令和权限规则**。
- 默认只读：第 1、2 步不修改任何东西。
- 第 3 步起会删除文件、改 agent 配置。每一步都要先向用户说明会改什么，得到明确同意后再做。
- 状态：实验性。作者在 macOS 上做过只读预演，删除逻辑也在隔离目录里测过（闲置目录被删；近期写入、进程占用、被 git 追踪、配置根目录之外、根目录本身都会被跳过）。会话结束自动回收还没有经过长期使用验证。请让用户知道这一点。
- 只处理 Rust/cargo 编译目录。`node_modules`、`dist` 这类目录不在范围内：名字叫 `dist` 的目录可能是签入仓库的 vendored 文件，作者就因此误删过一次。

## 第 1 步：只读检查，给用户一份报告

列出 cargo 编译目录及大小。把 `<项目根目录>` 换成用户放代码的目录：

```bash
find <项目根目录> -type d \( -name target -o -name 'target-*' \) -prune 2>/dev/null \
  | while read -r p; do [ -f "$p/CACHEDIR.TAG" ] && du -sh "$p"; done | sort -hr | head -20
```

如果用户或 agent 设置过 `CARGO_TARGET_DIR`，也看一下那个目录。

报告给用户：总共多大、最大的几个在哪、是否在 agent worktree 里（例如 `.claude/worktrees/`）。到这里停下，问用户想不想继续。

## 第 2 步：下载脚本并预演（仍然只读）

用户同意后：

1. 下载 <https://owenshen.top/skills/agent-build-gc/gc.py>，保存到 `~/.local/share/agent-build-gc/gc.py`。
2. **先完整读一遍脚本**，用几句话告诉用户它会做什么；有任何你看不懂或不放心的地方，就停下来说明。
3. 写配置 `~/.config/agent-build-gc/config.json`，`project_roots` 用第 1 步确认过的目录：

   ```json
   {
     "project_roots": ["~/code"],
     "env_allow": [],
     "session_root": "~/.cache/cargo-target-agent",
     "idle_hours": 24
   }
   ```

4. 预演：`python3 ~/.local/share/agent-build-gc/gc.py sweep`
   不加 `--delete` 时只写日志、不删除。日志在 `~/.local/state/agent-build-gc/gc.log`，每一行是 `DRY`（会删）、`SKIP`（跳过及原因）或 `REFUSE`（不在允许范围内）。

把预演结果给用户看。

## 第 3 步：清理一次（会删除文件，需要同意）

用户确认预演列表后：`python3 ~/.local/share/agent-build-gc/gc.py sweep --delete`

脚本每次删除前都会检查：目录在配置的根目录内、确实是 cargo 编译目录、没有被 git 追踪、最近 24 小时没写入、没有进程打开它、没有 cargo 进程在用它。删完用 `df -h` 告诉用户释放了多少。

## 第 4 步（可选）：会话结束自动回收

仅适用于 Claude Code。这一步会修改用户的 agent 配置，影响之后所有会话，必须先获得明确同意。

在 `~/.claude/settings.json` 的 `hooks` 里**追加**（不要覆盖已有的 hook）：

```json
{
  "hooks": {
    "SessionStart": [{ "hooks": [{ "type": "command", "command": "python3 ~/.local/share/agent-build-gc/gc.py session-start", "timeout": 5 }] }],
    "SessionEnd":   [{ "hooks": [{ "type": "command", "command": "python3 ~/.local/share/agent-build-gc/gc.py session-end", "timeout": 5 }] }]
  }
}
```

- `SessionEnd`：会话结束时，在后台删掉这个会话的专属编译目录，以及它所在 `.claude/worktrees/` worktree 里的 `target/`。
- `SessionStart`：只对 `env_allow` 里列出的仓库，给会话设置专属的 `CARGO_TARGET_DIR`。每个会话用自己的目录，不必和别人抢锁。默认 `env_allow` 为空，也就是不设置。
- **不要把打包脚本里写死了 `target/release/...` 路径的仓库加进 `env_allow`**，否则打包会找不到产物。加入前先在仓库里搜一下 `target/release`。

`SessionEnd` 不保证每次都触发（例如崩溃或强制退出），所以建议再加一个每天运行的定时任务，执行 `gc.py sweep --delete`。是否添加由用户决定。

## 没有 hook 的 agent

如果你不是 Claude Code，或者用户不想装 hook，把下面这条记进你的长期规则（得到用户同意后）：

> 在 worktree 里编译过 Rust 的任务，结束前确认 `target/` 已不再需要，按第 3 步的检查清单删掉它，并在交付说明里写明释放了多少空间。

## 不要做的事

- 不要用 `rm -rf "$A/$B"` 这种拼接变量的删除命令。变量为空时它会变成删除根目录。要么用字面绝对路径，要么写成 `"${A:?}/${B:?}"`。
- 不要按目录名批量删除 `dist`、`build`、`out`。
- 不要删正在被开发服务器或编译进程使用的目录，以 `lsof` 的结果为准。
- 不要替用户清空废纸篓。
