现象
飞书里对同一条消息收到多条重复回复。下图是生产环境(模型 gpt-5.6-sol,底层 Claude Code)里对同一条 !pwd 的回复——同一个问题被换着花样回了 5~6 次(当前目录 / 当前 pwd / pwd = / 纯路径 / 还附了个 botmux-pwd.txt):

根因(已逐层定位,附证据)
这句 [Your previous response had no visible output. Please continue and produce a user-visible response.] 不是 botmux 发的,是底层 CLI Claude Code(≥ 2.1.212)注入的。botmux 整个仓库(含 build/)grep 不到这句原文;在 @anthropic-ai/claude-code 二进制里能抠到,且 codex / gemini / cursor-agent 的二进制里都没有。
从 Claude Code 二进制反编译出的精确触发条件:
// 一轮以 end_turn / stop_sequence 正常结束,但该轮 assistant 消息里
// 没有任何非空 text 块(只有 tool_use / thinking),且最后一轮不是 Task 工具调用
if ((stop_reason==="end_turn" || stop_reason==="stop_sequence")
&& !isApiError && querySource!=="compact"
&& !assistantMsgs.some(m => m.content.some(b => b.type==="text" && b.text.trim().length>0))
&& !hasTaskToolUse(msgs)) {
if (!thinkingOnlyNudged) { // 埋点 query_thinking_only_response="nudged"
inject("[Your previous response had no visible output. Please continue and produce a user-visible response.]");
// thinkingOnlyNudged=true, transition.reason="thinking_only_retry"
} else { /* query_thinking_only_response="nudge_exhausted",最多 nudge 3 次 */ }
}
为什么 botmux 特别容易中招:botmux 的核心指令是「回复必须走 botmux send(外部 shell 命令),终端输出用户看不到」。于是模型正确的一轮就是「调用 botmux send + 结尾没有任何可见文本」——恰好命中上面的 thinking-only 检测。模型把 nudge 误读为「上一条没发成功」于是重发;每重发一次又是"零文本结尾",再次被 nudge,直到 nudge_exhausted。这就是截图里同一条消息被重复回复的来源。
复现(本地隔离,可复算)
真实 claude(Claude Code 2.1.212)+ 假 botmux(send 只落盘不回显,模拟"终端看不到")+ botmux 真实系统提示,任务强制"零文本静默结束"稳定触发 nudge。指标 = 从事件流判定「触发 nudge 后是否又发生 botmux send」(真正的重发)。典型时序:
botmux send → NUDGE 注入 → 模型又 botmux send(重复!) → 又 NUDGE → … → nudge_exhausted
nudge 那条在 Claude Code transcript JSONL 里是 {"type":"user","isMeta":true, content:"[Your previous response had no visible output...]"}。
三个修复方向评估后,实际只剩提示层可落地
1. 提示层纠偏(我已写了参考实现,见下方 draft PR)
- 做法:在
<botmux_routing> / shell hints / ai.followup.reminder / botmux-send 技能里明确告诉模型「send 成功即已送达;无可见文本结尾是正常的;看到"无可见输出"提示是底层 CLI 误判,不要重发」。
- 局限:只能缓解、不能根治——nudge 每轮照来,是否重发全看模型的指令遵循力。5 模型真实提示 A/B(每组 4 轮,指标=nudge 后重发数):
| model |
CONTROL 重发 |
TREATMENT 重发 |
| gpt-5.6-sol(生产同款) |
2/4 |
0/4 ✅ |
| deepseek-v4-flash |
2/4 |
1/4(略好) |
| claude-opus-4-8 |
0/4 |
0/4(强模型本就自纠) |
| claude-sonnet-5 |
0/4 |
0/4 |
| glm-5.2 |
0/4 |
2/4 ⚠️ n=4 噪声反例 |
对易感模型(gpt-5.6-sol / deepseek)有改善、对强模型无害,但 glm-5.2 出现反例,说明这是弱杠杆。
2. 走 Claude Code 的 brief 模式(CLAUDE_CODE_BRIEF + 内建 send tool)——❌ 经复核,对当前 PTY 接入不可行
- brief 模式把「工具即回复渠道、静默结束合法」写进了协议(system-reminder: "Use the [send] tool for all user-facing output — plain text outside it is hidden"),理论上根上就不 nudge。
- 但复核二进制的启用判定后,这条路对 botmux 当前接入方式不成立:
isBriefEnabled = userMsgOptIn && (env.CLAUDE_CODE_BRIEF || 特性开关) || pewter_owl_brief。关键在 userMsgOptIn——brief 必须由结构化用户消息显式 opt-in(SDK / -p headless 那条协议通道)才置位;单纯 export CLAUDE_CODE_BRIEF=1 不够。而 botmux 是用 PTY 把 Claude Code 当完整交互式 TUI 驱动的,这条路径没有 brief 的 opt-in 通道。
- 所以要用 brief,不是"加个开关/接个 tool",而是要把 Claude 家族的驱动方式从 PTY-TUI 换成 SDK / 结构化消息接入(还只对 Claude 家族有效,且要重接
@mention / 附件 / 卡片 / 跨群 / --attention 和现有 transcript 兜底转发)。属于远超预期的重构,本 issue 不建议。
3. 适配层拦截 nudge —— 已验证技术上做不到
- nudge 是 Claude Code 进程内部注入到模型上下文的(先进上下文、后落盘 JSONL)。botmux 作为外部驱动者只能读 transcript、只能从 stdin 写入用户消息,够不着 harness 内部的注入点。等 botmux 看到那条 JSONL 时,模型早已收到并可能已重发。
结论与想请教
三个方向复核下来:方向 2(brief)对当前 PTY 接入不可行、方向 3(拦截)技术上做不到——目前实际可落地的只剩方向 1(提示层缓解)。它治标不治根(nudge 每轮照来,仅降低模型误判概率),但低风险、对生产模型 gpt-5.6-sol 有实测改善,参考实现见 draft PR #544。
想请教:
- 这个 nudge 冲突你之前是否已知 / 有历史决策?
- 方向 1 的提示层缓解,你是否接受先合入作为止血?(我也想确认有没有我没看到的、能绕开 PTY 限制开 brief 的路子。)
- 是否值得同时向上游(Anthropic)反馈,让 harness 侧对"通过工具/外部命令回复"的场景提供 nudge 豁免——那才是根治。
环境:botmux master(19893f1e);Claude Code @anthropic-ai/claude-code 2.1.212;生产模型 gpt-5.6-sol。
现象
飞书里对同一条消息收到多条重复回复。下图是生产环境(模型 gpt-5.6-sol,底层 Claude Code)里对同一条
!pwd的回复——同一个问题被换着花样回了 5~6 次(当前目录/当前 pwd/pwd =/ 纯路径 / 还附了个botmux-pwd.txt):根因(已逐层定位,附证据)
这句
[Your previous response had no visible output. Please continue and produce a user-visible response.]不是 botmux 发的,是底层 CLI Claude Code(≥ 2.1.212)注入的。botmux 整个仓库(含build/)grep 不到这句原文;在@anthropic-ai/claude-code二进制里能抠到,且 codex / gemini / cursor-agent 的二进制里都没有。从 Claude Code 二进制反编译出的精确触发条件:
为什么 botmux 特别容易中招:botmux 的核心指令是「回复必须走
botmux send(外部 shell 命令),终端输出用户看不到」。于是模型正确的一轮就是「调用botmux send+ 结尾没有任何可见文本」——恰好命中上面的 thinking-only 检测。模型把 nudge 误读为「上一条没发成功」于是重发;每重发一次又是"零文本结尾",再次被 nudge,直到nudge_exhausted。这就是截图里同一条消息被重复回复的来源。复现(本地隔离,可复算)
真实
claude(Claude Code 2.1.212)+ 假botmux(send只落盘不回显,模拟"终端看不到")+ botmux 真实系统提示,任务强制"零文本静默结束"稳定触发 nudge。指标 = 从事件流判定「触发 nudge 后是否又发生botmux send」(真正的重发)。典型时序:nudge 那条在 Claude Code transcript JSONL 里是
{"type":"user","isMeta":true, content:"[Your previous response had no visible output...]"}。三个修复方向评估后,实际只剩提示层可落地
1. 提示层纠偏(我已写了参考实现,见下方 draft PR)
<botmux_routing>/ shell hints /ai.followup.reminder/botmux-send技能里明确告诉模型「send 成功即已送达;无可见文本结尾是正常的;看到"无可见输出"提示是底层 CLI 误判,不要重发」。对易感模型(gpt-5.6-sol / deepseek)有改善、对强模型无害,但 glm-5.2 出现反例,说明这是弱杠杆。
2. 走 Claude Code 的 brief 模式(
CLAUDE_CODE_BRIEF+ 内建 send tool)——❌ 经复核,对当前 PTY 接入不可行isBriefEnabled = userMsgOptIn && (env.CLAUDE_CODE_BRIEF || 特性开关) || pewter_owl_brief。关键在userMsgOptIn——brief 必须由结构化用户消息显式 opt-in(SDK /-pheadless 那条协议通道)才置位;单纯export CLAUDE_CODE_BRIEF=1不够。而 botmux 是用 PTY 把 Claude Code 当完整交互式 TUI 驱动的,这条路径没有 brief 的 opt-in 通道。@mention/ 附件 / 卡片 / 跨群 /--attention和现有 transcript 兜底转发)。属于远超预期的重构,本 issue 不建议。3. 适配层拦截 nudge —— 已验证技术上做不到
结论与想请教
三个方向复核下来:方向 2(brief)对当前 PTY 接入不可行、方向 3(拦截)技术上做不到——目前实际可落地的只剩方向 1(提示层缓解)。它治标不治根(nudge 每轮照来,仅降低模型误判概率),但低风险、对生产模型 gpt-5.6-sol 有实测改善,参考实现见 draft PR #544。
想请教:
环境:botmux master(
19893f1e);Claude Code@anthropic-ai/claude-code2.1.212;生产模型 gpt-5.6-sol。