Skip to content

fix(cli): send --help 打印帮助而非等待 stdin - #1412

Merged
deepcoldy merged 1 commit into
masterfrom
fix/send-help-flag
Sep 17, 2026
Merged

deepcoldy merged 1 commit into
masterfrom
fix/send-help-flag

Conversation

@deepcoldy

Copy link
Copy Markdown
Owner

问题

botmux send --help 从来不打印帮助。cmdSend 完全没有 --help 拦截,而正文解析发生在任何 flag 处理之前:positionals()--help 当 flag 滤掉 ⟹ 零位置参数 ⟹ 直接落进 readStdin()。于是分两种形态:

stdin 形态 实际行为
能读到 EOF(管道 / </dev/null exit 1「没有内容可发送。」247 字节,一行帮助都没有
永远不关闭(relay shell 交给 CLI 的 socket) 等 EOF 等不到,进程无限期驻留,并把调用它的 shell 一起堵住

send 的帮助正文其实写得很全(--images--anyway 逐项,38 行),但只内联在全局帮助里,没有任何入口能单独打印它

src/i18n/zh.tsai.routing.usage_attachments 写着「附件:…(详见 botmux send --help)」,经 src/adapters/cli/shared-hints.ts 注入进每个 bot 的 system prompt(英文孪生在 src/i18n/en.ts)⟹ 我们自己的文档在教所有 bot 跑一条不工作的命令,跑了还会堵住 shell。实测本机曾有 4 个 send --help 进程驻留 7.6 小时 ~ 3.4 天(fd0=socket:[…]、state=S、零 mention flag),各自堵着一个活着的 relay shell。

顺带一个证据:bun dist/cli.js send --helpbun dist/cli.js send --mention ou_x:n --no-mention --help 修前输出逐字节相同——因为两条都没走到任何 flag 校验,都只是在等 stdin。

改动

  • 抽出 SEND_HELP_BODY 常量作为单一来源:全局 botmux --help 由内联 38 行改为插值引用同一份,不再两处各写一份(避免日后漂移)。
  • cmdSend 首条语句拦截 --help / -h,打印后 return(退出码 0 —— 帮助是成功不是错误)。放在最前面是必要条件而非风格选择:任何排在正文解析之后的位置都会退化成等 stdin,就是上面那个缺陷本身。这一点在代码里留了注释说明,避免以后被「顺手挪到参数校验旁边」。
  • 帮助正文补 --help, -h 打印本帮助并退出 自描述行。
  • --help 优先级高于其它 flag:与 --mention / --no-mention(含二者冲突这种本会被 @ 硬门以 exit 2 拒掉的组合)混给时,仍然打印帮助。

其余 stdin 入口的超时 / --no-stdin 逃生阀不在本 PR 范围——那涉及所有 stdin 入口,值得单独评估。本 PR 只修 --help 拦截这一件事。

影响面

只动 cmdSend 入口与帮助文本的组织方式:

  • 不碰发送逻辑、@ 硬门(validateMentionDecision)、附件、卡片、语音、transport 与任何会话类型;
  • --help / -h 之外的 argv 形态,行为逐字不变(下面回归三项为证);
  • 跨 CLI / 跨后端 / 跨平台无影响:改动落在 CLI 参数解析层,不进 worker / PTY / 适配器 / core/ 共用路径,不存在 20+ 个 CLI 适配器或 PtyBackend vs TmuxBackend 的连带面;
  • 唯一的行为变化面是「send --help 现在会打印并退出 0」,而这正是文档一直承诺的行为。

验证

bun run build → exit 0。

行为(bun dist/cli.js,修前 / 修后对照)

命令 修前 修后
send --help exit 1,247B,零帮助 exit 0,3562B / 38 行帮助
send -h exit 1 exit 0,与 --help 输出逐字节一致
send --no-mention --help exit 1 exit 0,同上逐字节一致
send --mention ou_probe:probe --no-mention --help exit 1 exit 0,同上逐字节一致
send --help,stdin 为 socket 且对端不关闭 永久挂起 exit 0

最后一项是本缺陷最要命的形态,用 socket 对端 held-open 复现:修前挂到探针超时为止,修后 exit = 0 | help bytes = 3562

回归(三项均与修前一致)

  • 无正文且无 --helpsend </dev/null)→ 仍 exit 1「没有内容可发送。」
  • --mention--no-mention 同给且有正文 → 仍 exit 2,@ 硬门照常拒绝
  • 全局 botmux --help → 仍完整渲染 send 段,17345 字节,且 grep -c "内联图片(可重复)" = 1(确认只有一份,没复制)

测试与反变异

test/cli-root-help.test.ts 新增 5 例(4 个 argv 形态 + 1 个 socket stdin 对端 held-open;子进程一律走 test/helpers/ts-runner.ts,不写 Node-only 的 process.execPath --import):

npx vitest run test/cli-root-help.test.ts
→ Tests 16 passed (16)        # 原有 11 + 新增 5

反变异(把 cmdSend 里的拦截删掉再跑):恰好这 5 例转红、原有 11 例仍绿 —— 精确覆盖,不是顺带绿。其中 socket 那例由 1.2s 变成 30s 打满超时,复现的正是线上那 4 个进程的驻留形态。

相关面回归cli-* / send / help / usage 共 51 个测试文件):

Test Files  51 passed (51)
Tests       1333 passed (1333)
exit 0

本 PR 基于 origin/master fed664e4a,与主干零重叠(git log HEAD..origin/master -- src/cli.ts test/cli-root-help.test.ts 为空),无需 rebase。

🤖 Generated with Claude Code

`botmux send --help` 从来不打印帮助。`cmdSend` 完全没有 `--help` 拦截,
而正文解析在 `--help` 之后才发生:`positionals()` 把 `--help` 当 flag 滤掉
⟹ 零位置参数 ⟹ 落进 `readStdin()`。于是:

- stdin 能读到 EOF(管道 / `</dev/null`)⟹ `exit 1`「没有内容可发送」,
  247 字节,一行帮助都没有;
- stdin 永远不关闭(relay shell 交给 CLI 的 socket)⟹ 等 EOF 等不到,
  进程无限期驻留,并把调用它的 shell 一起堵住。

`send` 的帮助正文其实写得很全(`--images` 到 `--anyway` 逐项),
但只内联在全局帮助里,没有任何入口能单独打印它。
而 `src/i18n/zh.ts` 的 `ai.routing.usage_attachments` 写着
「详见 `botmux send --help`」,经 `src/adapters/cli/shared-hints.ts`
注入进每个 bot 的 system prompt ⟹ 文档教大家跑一条不工作的命令。
实测本机曾有 4 个 `send --help` 进程驻留 7.6 小时~3.4 天,各自堵着一个 shell。

- 抽出 `SEND_HELP_BODY` 常量作为单一来源;全局 `botmux --help` 改为插值引用,
  不再各写一份(避免两处漂移)。
- `cmdSend` 首条语句拦截 `--help` / `-h`,打印后 `return`(退出码 0——
  帮助是成功不是错误)。放在最前面是必要条件而非风格选择:任何排在正文解析
  之后的位置都会退化成等 stdin。
- 帮助正文补 `--help, -h` 自描述行。
- `--help` 优先级高于其它 flag:与 `--mention` / `--no-mention`(含二者冲突这种
  本会被 @ 硬门拒的组合)混给时仍打印帮助。

其余 stdin 入口的超时 / 逃生阀不在本 PR 范围,另议。

只动 `cmdSend` 入口与帮助文本的组织方式,不碰发送逻辑、@ 硬门、附件、卡片、
transport 与任何会话类型;`--help` 之外的 argv 形态行为逐字不变。
跨 CLI / 后端 / 平台无影响——本改动在 CLI 参数层,不进 worker / PTY / 适配器。

`bun run build` exit 0。

行为(`bun dist/cli.js`):

| 命令 | 修前 | 修后 |
|---|---|---|
| `send --help` | exit 1,247B 无帮助 | exit 0,3562B 帮助 |
| `send -h` | exit 1 | exit 0,与 `--help` 逐字节一致 |
| `send --no-mention --help` | exit 1 | exit 0,同上 |
| `send --mention ou_x:n --no-mention --help` | exit 1 | exit 0,同上 |
| `send --help`(socket stdin 对端不关) | **永久挂起** | exit 0 |

回归(三项均与修前一致):无正文且无 `--help` 仍 `exit 1`「没有内容可发送」;
`--mention` 与 `--no-mention` 同给且有正文仍 `exit 2` 被 @ 硬门拒;
全局 `botmux --help` 仍渲染 send 段且只出现一次(17345 字节)。

测试:`test/cli-root-help.test.ts` 新增 5 例(4 个 argv 形态 + 1 个 socket stdin
对端 held-open)。`npx vitest run test/cli-root-help.test.ts` → 16 passed。
反变异:删掉拦截后**恰好这 5 例转红**、原有 11 例仍绿,其中 socket 那例由
1.2s 变成 30s 超时(复现的正是驻留形态)。
相关 51 个测试文件(`cli-*` / `send` / `help` / `usage`)→ 1333/1333 passed。

Co-Authored-By: Claude Code <noreply@anthropic.com>
@deepcoldy
deepcoldy merged commit 89a62b6 into master Sep 17, 2026
14 checks passed
@deepcoldy
deepcoldy deleted the fix/send-help-flag branch September 17, 2026 09:19
@github-actions

Copy link
Copy Markdown

🚀 Released in v3.24.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