# 你的 AI 助手，正在悄悄污染你的电脑环境


你的 Agent 给你乱装工具了吗

<!--more-->

## 起因：我只是想看看装了啥

有一天我打开终端，随口问了一句：

> 帮我看一下 Homebrew 安装了啥，列一下都是干啥的。

然后我看到了一个让我愣住的列表：**175 个 formula、4 个 GUI 应用**。

问题不在于数量多。问题是，里面绝大多数我根本不认识，更不可能是我主动安装的。

真正引起警惕的是这句话：

> 其余都是啥的依赖？太多了我看着。

当用户开始对一台新电脑产生“垃圾场”的感觉时，就该认真查一次了。

## 问题一：agent 为什么会乱装工具？

现在电脑里不止 Codex，还有 Claude Code、Gemini、OpenCode、WorkBuddy、QoderWork、Trae、Cursor、QClaw 等一堆 agent。

它们有一个共同特点：**工具缺失时，第一反应不是“问用户”，而是“自己装一个”。**

背后的原因并不难理解：

1. 技能文档里写着 `brew install poppler`、`brew install tesseract`，agent 照做。
2. agent 把“任务完成”看得比“环境干净”重要。
3. agent 没有“这是我的电脑”的主人翁意识。
4. 用户没有在规则里告诉它：安装前要问、用完要删。

尤其危险的是这类组合：

- 一次性的 PDF 转换，结果装了 `poppler` 全家桶。
- 一次性的视频处理，结果装了 `ffmpeg-full` 加 99 个依赖。
- 一次性的 OCR，结果装了 `tesseract` 和语言包。
- agent 明明看到你在用 `nvm`，还是执行了 `brew install node`。

于是系统环境越来越乱，但没有任何一个 agent 觉得自己做错了。

## 问题二：乱装之后会留下什么？

以一个典型的“媒体处理”安装为例：

```text
ffmpeg-full          → 拉入约 99 个依赖
imagemagick-full     → 拉入约 59 个依赖
poppler              → 拉入约 45 个依赖
tesseract-lang       → 拉入约 37 个依赖
cfr-decompiler       → 拉入约 26 个依赖
```

这些数字是**已安装的传递依赖数量**，不是“安装文件数”。

而且它们之间高度重叠：

- 一堆 X11 图形库
- 一堆图片编解码库
- 一堆字体库
- 一堆压缩库
- 一堆加密库

你可能只是想把 PDF 转成文字，但系统却多出了几十个以后永远不会主动用到的 C 库。

更麻烦的是重复安装：

```text
nvm 已经装好了 node@22
agent 又执行：brew install node
```

结果就是：

- 两套 Node
- 两套 PATH
- 全局包装到了错误的位置
- 你删掉一个，另一个 agent 又装回来

## 排查思路：先分清楚“我装的”和“依赖”

真正有用的第一刀，不是 `brew list`，而是这条命令：

```bash
brew list --formula --installed-on-request
```

它列出的是**你明确要求安装的 formula**，而不是被其他包拖进来的依赖。

完整排查链路如下：

```bash
# 1. 全量安装清单
brew list --formula
brew list --cask

# 2. 哪些是你主动装的
brew list --formula --installed-on-request

# 3. 哪些是顶层包（没有被其他已安装包依赖）
brew leaves

# 4. 版本
brew list --versions

# 5. 某个包拉入了哪些依赖
brew deps --installed <包名>

# 6. 反向查：某个依赖被谁使用
brew uses --installed <依赖名>

# 7. 可自动清理的孤儿
brew autoremove --dry-run

# 8. 是否有依赖缺失
brew missing
```

实战中我发现的最有价值的组合是：

```bash
for p in $(brew list --formula --installed-on-request); do
  echo "$p: $(brew deps --installed "$p" | wc -w) deps"
done | sort -t: -k2 -nr
```

它直接告诉我：谁才是“依赖大户”。

结果非常清晰：

```text
ffmpeg-full       99 deps
imagemagick-full  59 deps
poppler           45 deps
tesseract-lang    37 deps
cfr-decompiler    26 deps
```

## 清除方法：删根包，不要手动删依赖

最常见的错误是：看到一堆依赖，一个个 `brew uninstall`。

正确做法是：

```bash
# 删掉主动安装的大包
brew uninstall ffmpeg-full imagemagick-full poppler tesseract-lang

# 让 Homebrew 自己判断还有哪些依赖没人用
brew autoremove
```

Homebrew 会自动清掉那些“只被已删除包需要”的依赖。

不过这里有一个坑：**依赖环会让自动清理失效**。

我这次遇到一组：

```text
libtiff <-> webp
```

两者互相依赖，形成一个环，于是 `brew autoremove` 认为它们都不是孤儿：

```text
giflib
jpeg-turbo
libpng
libtiff
lz4
webp
xz
zstd
```

这 8 个包已经没有任何“主动安装”的包在使用了，但自动清理扫不出来。

解决办法是手动拆环：

```bash
brew uninstall libtiff webp
brew autoremove
```

拆掉环之后，剩下的 6 个依赖也被自动清掉了。

清理后，整机从 175 个 formula 降到 24 个，并且：

```bash
brew autoremove --dry-run   # 无输出
brew missing                # 无输出
```

## 问题三：skill 才是真正的“安装入口”

清完 Homebrew 后，我又做了一次更深的排查：

> 这些包是不是某个 agent skill 要求安装的？

于是我把所有 agent 的配置目录都翻了一遍：

| Agent | 主要配置目录 |
|---|---|
| Codex | `~/.codex/`、`~/.agents/skills/` |
| Claude Code | `~/.claude/` |
| Gemini | `~/.gemini/` |
| OpenCode | `~/.config/opencode/` |
| WorkBuddy | `~/.workbuddy/` |
| QoderWork | `~/.qoderwork/`、`~/.qoderworkcn/` |
| Trae | `~/.trae/`、`~/.trae-cn/` |
| CodeBuddy CN | `~/.codebuddy/` |
| QClaw | `~/.qclaw/` |

每个目录里再搜 skills 和 MCP：

```bash
find ~/.workbuddy ~/.qoderwork ~/.qclaw ~/.trae ~/.codebuddy \
  -iname 'SKILL.md' -o -iname '*mcp*.json' 2>/dev/null
```

然后在所有 skill 里搜 Homebrew 安装指令：

```bash
rg -n -i 'brew install|homebrew' \
  ~/.workbuddy/skills \
  ~/.qoderwork/skills \
  ~/.qclaw/skills \
  ~/.trae/builtin \
  ~/.codebuddy/skills-marketplace \
  ~/.claude/plugins 2>/dev/null
```

搜索结果比我预想的更密集：

- WorkBuddy 的 `markitdown-skill`：`brew install tesseract`
- QoderWork 的 `docx` skill：`brew install pandoc`、`brew install poppler`、`brew install --cask libreoffice`
- CodeBuddy 市场 skills：大量 `ffmpeg`、`poppler`、`tesseract`、`ghostscript`、`libreoffice`、`webp`、`node@20`、`python@3.11`
- QClaw 的 `qclaw-env`：直接是一个“环境安装器 skill”，覆盖 node、python、go、uv、gh、jq、ripgrep、ffmpeg 等
- Claude 官方插件市场：LSP 插件、code-review、plugin-dev 等都写了 brew 安装命令

结论很明确：**agent 不是随机乱装的，它是照着 skill 文档装的。**

所以要管住 agent，就要管住它的规则文件。

## 制定约束：一套可以给所有 agent 用的安装守则

我最终沉淀了一套规则，核心如下：

```text
环境安装守则：
1. 禁止直接执行 brew install / npm install -g / pip install --global / go install，
   除非用户明确同意。
2. 安装前必须检查现有环境：
   - command -v <工具>
   - nvm ls（Node）
   - pyenv versions（Python）
   - sdk list java / sdk current（Java、Maven）
3. Node 只能用 nvm；Python 只能用 pyenv；Java/Maven 只能用 SDKMAN。
4. 一次性工具优先使用 npx、pipx、uvx、docker run --rm，用完即弃。
5. 必须 brew install 时，先确认，用后立即 uninstall + autoremove。
6. 安装前说明：装什么、装到哪里、是否写 PATH、是否自动清理。
7. 优先复用项目环境：.venv/bin、node_modules/.bin、$(go env GOPATH)/bin。
```

这套规则解决三个真实痛点：

1. **不拦着完成任务**，只拦着污染环境。
2. **给出替代方案**，否则 agent 还是会选择它最熟悉的方式。
3. **把“装完怎么收尾”也写进去**。

## 给不同 agent 加约束

不同 agent 的规则读取位置不一样，我按实际目录整理了一份：

| Agent | 放哪里 |
|---|---|
| Codex | 工作区 `AGENTS.md` |
| Claude Code | `~/.claude/CLAUDE.md` |
| Gemini | 工作区 `AGENTS.md` 或会话开头 |
| OpenCode | 工作区 `AGENTS.md` |
| WorkBuddy | `~/.workbuddy/skills/install-policy/SKILL.md` |
| QoderWork | `~/.qoderwork/skills/install-policy/SKILL.md` |
| QClaw | `~/.qclaw/skills/qclaw-rules/` |
| Trae / Trae CN | 自定义 skill 或规则设置 |
| CodeBuddy CN | App 设置或自定义 skill |
| Cursor | `.cursor/rules/*.mdc` |

以 Claude Code 为例，直接把规则追加到：

```bash
echo '# 环境安装守则
- 禁止直接执行 brew install / npm install -g。
- 安装前先检查 nvm、pyenv、sdkman。
- 一次性工具优先 npx、pipx、uvx、docker run --rm。
- 必须 brew 时先询问，用后 uninstall + autoremove。' >> ~/.claude/CLAUDE.md
```

以 QoderWork 为例，可以新建一个自定义 skill：

```markdown
---
name: install-policy
description: 环境安装守则。任何安装类任务必须先读取本 skill。
---

1. 禁止 brew install node、python、openjdk。
2. 优先 nvm、pyenv、SDKMAN 已有版本。
3. 一次性工具用 npx、pipx、uvx、docker run --rm。
4. 必须 brew install 时先询问用户，用后立即清理。
```

放到：

```text
~/.qoderwork/skills/install-policy/SKILL.md
```

QClaw 则可以直接修改已有规则：

```text
~/.qclaw/skills/qclaw-rules/
```

## 一次性工具的“用完即弃”方案

如果以后真的需要临时工具，建议按这个表选：

| 场景 | 方式 |
|---|---|
| npm 工具 | `npx 包名` |
| Python 工具 | `pipx run 包名` 或 `uvx 包名` |
| Go 工具 | `go run`，不写 GOPATH |
| 音视频处理 | `docker run --rm -v $PWD:/work -w /work <镜像>` |
| PDF 处理 | 带 poppler 的 docker 镜像 |
| 临时二进制 | 下载到 `/tmp`，用完删除 |
| 项目内工具 | `.venv/bin`、`node_modules/.bin` |

原则只有一个：**让它活不过这次任务。**

## 定期体检命令

给 agent 定好规矩之后，也不能完全不管。建议每隔一段时间跑一遍：

```bash
# 我主动装了什么
brew leaves
brew list --formula --installed-on-request

# 有没有孤儿
brew autoremove --dry-run

# 有没有坏依赖
brew missing

# 谁是大户
for p in $(brew list --formula --installed-on-request); do
  echo "$p: $(brew deps --installed "$p" | wc -w) deps"
done | sort -t: -k2 -nr | head -20

# 哪些 skill 想装东西
rg -n -i 'brew install' ~/.claude ~/.gemini ~/.workbuddy ~/.qoderwork \
  ~/.qclaw ~/.trae ~/.codebuddy 2>/dev/null | head -50
```

## 结尾

Agent 会越来越主动，这是趋势。

但“主动”不等于“自作主张”。工具安装应该是环境治理中最需要权限控制的一类操作，因为它不可见、累积快、清理成本高。

给每个 agent 加一条规则，比事后清理一百次都有效：

> 先看有没有，再问要不要，用完必须走。

干净的系统不是靠一次清理，而是靠一套约束。

## 附录：本次使用的主要排查命令

```bash
brew list --formula
brew list --cask
brew list --versions
brew list --formula --installed-on-request
brew leaves
brew deps --installed <包名>
brew uses --installed <依赖名>
brew autoremove --dry-run
brew autoremove
brew missing
```

