fix(pm2): 修复大注册表读取截断 - #1026
Conversation
|
@codex review |
|
To use Codex here, create a Codex account and connect to github. |
|
@codex review |
|
Codex Review: Didn't find any major issues. You're on a roll. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
DeepColds
left a comment
There was a problem hiding this comment.
结论:通过,未发现阻塞问题。
- 修复点与根因匹配:以
stdout.write的 completion callback 作为完整写入边界,避免大于 pipe 高水位时process.exit(0)截断尾部。 - 先完成 stdout 写入,再断开 PM2 RPC 并退出;未改变只读 observer 与 fail-closed 语义。
- 改动范围集中在
jlist输出路径,不影响 status/logs、CLI adapter、会话后端或飞书链路。
本地验证:
pnpm build:通过。pnpm exec vitest run --project unit test/stdout-flush.test.ts test/desktop/desktop-pm2-apps.test.ts test/pm2-jlist.test.ts test/shutdown-supervisor-contract.test.ts:4 个文件、48 个测试通过。- 额外以子进程 pipe 写入 4 MiB payload:实际收到 4,194,304 / 4,194,304 bytes,退出码 0。
完整 pnpm test 也已执行;本环境中有 6 个与本 PR 无关的既有/环境敏感失败,其中 PM2 cgroup、进程名和 redirect 环境项可在当前 master 复现,不影响本次结论。
|
感谢这个修复 🙏 根因分析非常准确,修法也是最小改动。我在本地做了独立验证,确认这是个真实的生产 bug,先说结论:方向、根因、修法都没问题,只有新增测试的有效性想和你商量一下。 复现与修复效果都已坐实因为是 fork PR 不跑 CI,我在 Linux / Node 22 上复刻了
我还把因果链闭合到了用户看到的那句报错:把真实 payload 截到 146176 字节喂进真的 回归面也确认干净: 另外顺手枚举了全仓同类写法,可以确认没有第二个未修实例: 一个补充:影响面可能比描述里更广描述提到「在 macOS 上首次稳定复现」。我实测 Linux 下同样必中(上表数据就是 Linux 跑的)。因为 daemon 生产环境跑在 Linux,这其实是生产路径上的真实 bug,不只是 mac 本地开发的困扰 —— 这点或许值得在描述里改得更准,也更能说明这个修复的价值。 主要建议:
|
|
补充两点更正与更精确的信息(复审交叉后得到,其中一条是修正我上一条评论里的说法): 1. 更正:真实截断阈值是 ~146 KiB,不是我上面说的 64 KiB我上一条评论按「超过 pipe 高水位 64 KiB 就会截断」来描述,这不够准确。我做了逐档实测(Linux / Node 22.21.1,
两个反直觉的点:
这不改变这个 PR 的结论(jlist 实测 474 KB ~ 878 KB,远超阈值,必中),但对写测试有实际影响:如果只把 payload 顶到「刚过 64 KiB」,测试会假绿(因为那个区间其实是安全的)。所以测试必须确保输出稳稳越过 ~146 KiB。 我上一条给的写法(6 进程 × 12 个 4000 字节 env)实测约 474 KB,是安全的;不过下面这个等价写法更简洁,也同样越过真实阈值(实测 474,752 字节),双向验证过有牙 —— 建议直接用这版: // 加进 test/pm2-readonly-cli.test.ts 的 describe 内(需补 writeFileSync import)
// 6 × 30KB env → jlist 实测 474,752 字节,稳稳越过 ~146 KiB 的同步冲刷阈值。
it('delivers a jlist past the synchronous flush ceiling without truncation', () => {
const home = tempHome();
const pm2Home = join(home, '.botmux', 'pm2');
mkdirSync(pm2Home, { recursive: true });
expect(spawnSync(process.execPath, [PM2_PATH, 'status'], {
env: { ...process.env, PM2_HOME: pm2Home },
stdio: 'ignore',
timeout: 10_000,
}).status).toBe(0);
const idleScript = join(home, 'idle.js');
writeFileSync(idleScript, 'setInterval(() => {}, 1000);\n');
for (let i = 0; i < 6; i++) {
const started = spawnSync(process.execPath, [PM2_PATH, 'start', idleScript, '--name', `flush-probe-${i}`], {
env: { ...process.env, PM2_HOME: pm2Home, BOTMUX_FLUSH_PROBE: 'x'.repeat(30_000) },
stdio: 'ignore',
timeout: 20_000,
});
expect(started.status).toBe(0);
}
const parsed = JSON.parse(captureReadonlyPm2Jlist({
pkgRoot: dirname(dirname(CLI_PATH)),
home: pm2Home,
})) as unknown[];
expect(parsed).toHaveLength(6);
}, 90_000);双向验证:打上修复 ✅ 通过(约 2.2s);撤掉修复 ❌ 死在 2. 更正:同模式还有第二处(不阻断本 PR,建议另开 follow-up)我上一条说「没有发现第二个未修实例」,这句话说过头了,交叉复审时被指出还有一处:
不过按上面测出的真实阈值,触发需要用户粘贴 ~14 万字符以上,比我原本估计的更低频。所以:不阻断这个 PR,建议单独记个 follow-up issue 即可。 另外顺带说明,仓库里其余同形状的站点我都核过是安全的: 同样是自动评审的补充意见,最终以维护者审阅为准。核心修复的结论不变:真 bug、修法正确、值得合入,只是测试希望能真正锁住回归 🙏 |
|
已根据评论更新并推送 bfe372f。
|
|
@codex review |
|
Codex Review: Didn't find any major issues. Keep them coming! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
bfe372f to
03c4d58
Compare
|
已基于最新 master(8c489709)完成 rebase 并强制更新分支。 冲突来自上游删除旧的 pm2-readonly-cli 生命周期测试;该测试绑定了已不再采用的 cgroup 生命周期假设。此次没有恢复旧测试,而是将本 PR 的大输出回归覆盖迁为独立的 test/pm2-readonly-jlist.test.ts,仅覆盖当前仍被桌面和插件调用的 read-only jlist helper。 已完成相关测试与 pnpm build;PR 描述中的测试路径和结果也已同步更新。 |
|
已基于最新 本次补充修复了复审中确认的同类 CLI 输出截断: 已实际运行:
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 20cc0bd426
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| afterEach(() => { | ||
| for (const home of homes.splice(0)) { | ||
| const pm2Home = join(home, '.botmux', 'pm2'); | ||
| spawnSync(process.execPath, [PM2_PATH, 'kill'], { |
There was a problem hiding this comment.
Route PM2 subprocesses through the test runner helper
The new regression test invokes PM2 directly with spawnSync(process.execPath, ...) here and again for status and start, bypassing the repository’s mandatory runtime-aware subprocess abstraction. This leaves the test and its teardown outside the supported Node/Bun launch contract, so they can fail or exercise different behavior when the suite runs under Bun; use spawnSyncTsScript from test/helpers/ts-runner.ts for all three invocations.
AGENTS.md reference: AGENTS.md:L28-L28
Useful? React with 👍 / 👎.
改了什么
修复两类由显式
process.exit()触发的大输出截断,并保留原有协议和退出码。jlisthelper:等待 stdout 写入完成后,才断开 RPC 并退出;botmux ask --json:用户自由文本回答没有长度上限,先完整写出 JSON 再按既有结果退出;sendrelay:宿主子进程的 stdout 和 stderr 均写完后,再镜像退出码。原因
Node 的 pipe 写入是异步的;调用
write()后立即退出,可能丢失尚未排空的尾部字节。PM2 registry 和ask --json的comment都可超过该边界,后者已用真实子进程复现。影响范围
jlisthelper 及依赖它做安全校验的 start、restart、stop 路径;ask的输出完成边界和 sandbox relay 的结果镜像边界,不改变 ask 请求格式、回答内容、退出码或 relay 授权逻辑;验证
pnpm exec vitest run --project unit test/ask-cli.test.ts test/pm2-readonly-jlist.test.ts test/pm2-command.test.ts test/pm2-existing-client.test.ts test/desktop/desktop-pm2-apps.test.ts test/shutdown-supervisor-contract.test.ts:32 通过;2 个 Linux 专用用例在 macOS 跳过;pnpm build:通过,包含 TypeScript、脚本类型检查、dashboard bundle 和 dist audit;ask回归测试使用真实 CLI 子进程与 fake daemon,返回 500,000 字符的 comment,并断言 stdout JSON 可完整解析且 comment 全量一致;