Skip to content

fix(core): legacy pm2 reaper 改为按 PATH 查找 pm2,不再执行探测泄漏 God - #1882

Merged
deepcoldy merged 2 commits into
masterfrom
fix/legacy-pm2-probe-leak
Oct 13, 2026
Merged

deepcoldy merged 2 commits into
masterfrom
fix/legacy-pm2-probe-leak

Conversation

@deepcoldy

Copy link
Copy Markdown
Owner

问题

legacy-pm2-reaper 的 resolvePm2Bin 用 pm2 --version 探测 PATH 上有没有 pm2,这个探测有两个问题:

  1. pm2 --version 并非只读,它会 daemonize 一个 God。旧代码把它跑在临时 PM2_HOME 下,然后 pm2 kill 并删除目录。但 pm2 kill 可能超时,目录一旦删掉,这个 God 就再也无法通过 PM2_HOME 找到并关掉。
  2. resolvePm2Bin 在 reapLegacyPm2 开头无条件执行,也就是每次 botmux start/restart 都会跑一遍,不管机器上有没有旧 God。

结果是:PATH 上装了 pm2 的机器,会随着启动次数不断累积孤儿 God。在开发机上实测发现 3 个空跑的 botmux-pm2-probe-<pid> God,最早的已经跑了好几天。

修复

  • resolvePm2Bin:遍历 PATH,用 accessSync(X_OK) 查找并返回绝对路径,不执行 pm2
  • reapLegacyPm2:只在 liveGodAt 确实发现旧 God 后才解析 pm2 CLI(惰性求值,结果缓存)。新装或已完成迁移的机器在常规启动时完全不碰 pm2
  • scripts/smoke-musl-reaper-probes.mjs 里的 pm2 可达性检查也改为文件查找,仓库中不再有 pm2 --version 调用

影响面

只改了 legacy-pm2-reaper 的 pm2 CLI 解析,影响 botmux start/restart/stop 的迁移清理路径。没有 PATH pm2 的路径(编译态二进制、CI)行为不变。有旧 God 时依然走 pm2 CLI,调用序列不变(jlist / delete / kill),只是少了 --version。代价是:如果 PATH 上的 pm2 可执行但实际跑不起来,之前探测阶段会把它判为不可用,现在要到后续 jlist 失败时才发现,并标记为 unresolved。这和 CLI 在运行中失败时的处理方式一致。

验证

  • npx tsc --noEmit、bun run build:通过
  • npx vitest run test/legacy-pm2-reaper.test.ts:26 个全部通过
    • 新增「无旧 God 时 PATH 上的 pm2 一次都不被执行」
    • 新增「有旧 God 时仍使用 PATH 上的 pm2,调用序列为 jlist / delete / kill,没有 --version」
    • 反向变异:撤掉 src 修复后,这两个用例都变红
  • node scripts/smoke-musl-reaper-probes.mjs dist/core/legacy-pm2-reaper.js:all checks passed
  • 跑完上述测试后,pgrep -f "God Daemon" 没有任何残留
  • bun test 下 treats a REAL orphaned socket … as no God 1 个用例失败(卡在 bun 默认的 5s 超时)。master 上同样失败,与本 PR 无关

🤖 Generated with Claude Code

`pm2 --version` 并非只读,会 daemonize 一个 God。旧探测把它跑在临时
PM2_HOME 里再 `pm2 kill` + 删目录,但 kill 可能超时,目录删掉后这个 God
就无人可寻址;而 resolvePm2Bin 在每次 `botmux start/restart` 都无条件执行,
PATH 上有 pm2 的机器会随启动次数累积孤儿 God(实测 `botmux-pm2-probe-*`)。

- resolvePm2Bin:遍历 PATH 用 accessSync(X_OK) 查找,返回绝对路径,不执行 pm2
- reapLegacyPm2:只在确实发现旧 God 时才解析 pm2 CLI,常规启动完全不碰 pm2
- smoke-musl-reaper-probes 的 pm2 可达性检查同样改为文件查找
- 测试:无旧 God 时 PATH 上的 pm2 一次都不被执行;有旧 God 时仍走 PATH 上的
  pm2(调用序列为 jlist / delete / kill,无 --version)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
仅 X_OK 会接受可遍历的同名目录,导致后续 pm2 调用 EACCES、旧 fleet 未被清理。
改用 src/utils/executable.ts 的 locateExecutable(要求普通文件)。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@deepcoldy

Copy link
Copy Markdown
Owner Author

Review 结论记录:

  1. 「PATH 查找会把同名目录当作 pm2」:已修复(76937a9ac)。改用 src/utils/executable.ts 的 locateExecutable,它要求候选是普通文件。新增的测试在 PATH 前面放了一个同名目录,旧写法下该测试失败(deleted 为空),新写法通过。
  2. 「PATH 空段(代表当前目录)不再被识别」:不改。
    • 触发它需要同时满足:PATH 含空段、执行 botmux start 时的 cwd 里正好有 pm2、旧 fleet 在共享的 ~/.pm2 下。这种组合极其罕见。
    • 按 cwd 解析可执行文件本身就是公认的安全隐患。仓库里所有 PATH 查找(locateExecutable,CLI 适配器、worker 等都在用)都刻意跳过空段,这里保持一致。
    • 即使真的命中,降级行为也是安全的:共享 home 走「无 CLI 时不碰」分支,botmux status 仍会提示检测到旧版 pm2。

@deepcoldy
deepcoldy merged commit ff0cdf6 into master Oct 13, 2026
20 of 23 checks passed
@deepcoldy
deepcoldy deleted the fix/legacy-pm2-probe-leak branch October 13, 2026 15:41
@github-actions

Copy link
Copy Markdown

🚀 Released in v3.43.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant