DeepSeek Harness bash 工具遇感叹号挂死 300 秒:最小复现与转义写法

故障排查发布于 2026-10-30作者: DeepSeek Plugin 插件市场
DeepSeek HarnessDSHbashhistory expansion超时转义命令行
DeepSeek Harness 的 bash 工具执行带 ! 的命令会空转到 300 秒超时,最后只回一屏 dsh> 提示符。用最小复现定位到引用形式与历史展开,并给版本状态与今天的规避写法。

一条 printf 'p4-start %s p4-end\n' '!',在 bash 工具里跑满 300 秒,最后返回的不是结果而是一屏 dsh> 提示符。 这条命令本身没有任何问题,在终端里秒出结果;挂死的原因是它在投递时被包成 $'...' 形式,而交互式 bash 在读行阶段就对 ! 做了历史展开,整行被丢弃(#10636)。下面按实验顺序复现并验证。

DeepSeek Harness 实验一:最小复现,只改一个字符

控制变量只有一处:命令里带不带 !。

  1. 先跑一条等价但不带感叹号的命令:printf 'p4-start %s p4-end\n' 'x',正常返回。
  2. 再跑 printf 'p4-start %s p4-end\n' '!',挂住。
  3. 观察投递形态:工具层把命令包成类似下面的字符串(含 START/END 标记与 eval --)。
  4. 把这串内容喂给与生产同构的 bash --noprofile --norc -i,输出是:
bash: !\: event not found
dsh>
  1. 注意两点:START 的 printf、eval、END 的 printf 全都没执行;错误之后那个提示符前面没有完成标记。
  2. 结论:不是命令写错,是整行在「读行」阶段就被 shell 丢了。

DSH插件 实验二:确认问题在引用形式,不在命令

$'...' 与单引号对 ! 的处理不同:历史展开豁免单引号里的内容,不豁免 ANSI-C 引用里的内容。

  1. 看 quoteForBash:它转义了反斜杠、单引号、\r、\n,唯独没处理 !。
  2. 看 shell 启动参数:--noprofile --norc -i,是交互模式,histexpand 默认开启。
  3. 看初始化序列:只有 stty -echo,没有关闭历史展开。
  4. 于是 ! 后面跟着被转义的单引号,被当成事件标识符 !\,查找失败后打印 event not found 并丢弃整行。
  5. 这一步把范围缩到了「引用形式」:命令内容怎么改都只是绕过,不是修复。

DeepSeek Harness 三层放大:为什么一行被丢弃会烧 300 秒

触发只是第一层,真正把 300 秒烧掉的是后两层:提示符缺完成标记,工具循环又不认这个状态。

  1. 层一(触发):历史展开丢弃整行,START/END 两个标记 printf 一起没了。
  2. 层二(放大):完成检测依赖 PROMPT_COMMAND 打印 OSC 133;D,但丢弃行之后 bash 重打的提示符不执行 PROMPT_COMMAND,于是只有裸提示符。
  3. 层三(空转):工具循环的返回分支只认「END 标记出现」「会话退出」「等待原因为 stdin_read」三种情况,inferred_idle 不在其中,于是每 25ms 空转一轮,直到 300 秒 deadline。
  4. 超时后走 timeout 分支,执行 shells.reset() 把整个 shell 杀掉。
  5. 现场表现:满屏 dsh>,event not found 被 scrollback 上限截掉,还会出现「开头被丢弃」的提示。

DeepSeek插件 实验三:验证两种修法

一种改引用形式,一种关掉历史展开;两者都能让这条命令正常返回,但只有后者进了 master。

方案层改法社区验证
B层一$'...' 换成传统单引号引用全链正常输出
C层一初始化追加 set +H全链正常输出
D层三连续空转且无输出增长时早退并提示韧性加固
E层二完成检测不只依赖 PROMPT_COMMAND韧性加固

方案 B 要注意保留对 \r、\n 字面量的处理;方案 C 只有一行,是根因修法。D 与 E 不解决触发,但能让未来同类「行被丢弃」快速失败,而不是烧满 300 秒。

DeepSeek Harness 版本状态:master 已修,rc.3 未回移

提交 967ba4c4b8(2026-10-21)把初始化从 stty -echo 改成 stty -echo; set +H,并补了回归测试;但它不是 rc.3 tag 的祖先。

  1. 用 git merge-base --is-ancestor 967ba4c4b8 <rc.3 提交> 可以自查包含关系。
  2. 社区结论:它是 master(22ddee3)的祖先,不是 dsh-v0.2.1-rc.3(1f81c04)的祖先。
  3. 所以在 rc.3 上仍会 100% 复现。
  4. 即便修复合入,层一依然没变:quoteForBash 还是只转义反斜杠、单引号、换行,set +H 是唯一挡在 ! 前面的东西。
  5. 换句话说,若将来换了后端或重写包装层,这个引用缺陷会立刻回来。

DSH插件 今天的规避:别让裸 ! 进命令

在修复到达你的版本之前,最稳的做法是让感叹号不经过历史展开的判断。

  1. 避免在命令里直接出现裸 !,用等价写法替代,比如用 printf 生成字面量。
  2. 如果必须带,确保它处在传统单引号引用里,而不是 ANSI-C 引用里。
  3. 不要把 set +H 写在命令行首期待生效——展开发生在读行阶段,那一行还没执行。
  4. 长任务拆成几条短命令,万一挂住也能快速定位到具体哪一条。
  5. 挂住之后不要反复重试同一个字符,先改成等价写法验证链接本身是通的。

注意事项

  1. 别把 300 秒当成网络慢。这里的超时点是本地 shell 空转,不是请求超时。
  2. 别只看最后一屏输出。event not found 可能已经被 scrollback 截掉。
  3. 别指望恢复会话。超时会重置 shell,之前的交互状态一并丢失。
  4. 记录 bash 版本。社区在 macOS 自带 3.2.57 上复现;5.x 的行为未在本机验证,修复不应依赖版本。
  5. 别把 B 和 C 当成互相替代。一个是引用形式正确,一个是关掉展开,纵深防御应该都做。

如果你的命令里根本没有 ! 却也挂到超时,那不属于这一类,去查完成检测与 shell 初始化。

最后补一句判断技巧:同类「命令被 shell 丢弃」的故障,最终表现往往是「标记没出现 + 输出为空 + 精确卡到超时上限」,记住这个组合能省掉大量猜测。

同理,heredoc 里出现 ! 也会走同一条路径——展开发生在整行读取时,跟它是不是多行写法无关。相关排查见 DeepSeek Harness 持久终端把 PSReadLine 预测补全带进上下文?三种关闭路径与验证。

常见问题

DeepSeek Harness 的 bash 工具执行带 ! 的命令会卡 300 秒,为什么?

因为 DeepSeek Harness 的命令投递层用 $'...' 包裹命令,而受控 shell 是交互式 bash,历史展开默认开启;$'...' 不豁免 !,整行被 bash 以 event not found 丢弃,完成标记也没打印,工具就一直空转到超时。

为什么最后只看到一屏 dsh> 提示符,DeepSeek Harness 却没给出任何报错?

因为整行被丢弃后,DeepSeek Harness 的 START 标记 printf 也没执行,超时后工具找不到起始标记就回退到 scrollback 尾部;真正的 event not found 被 scrollback 上限截掉,只剩一串裸提示符。

在命令里加 set +H,能当场解决 DeepSeek Harness 的这个卡死吗?

不能,在命令里写 set +H 救不了 DeepSeek Harness 的这次挂死。历史展开发生在读行阶段,行内语句还没执行;跨调用设置的 set +H 又会被超时后的 shell reset 抹掉。真正有效的写法是避免裸 !。

DeepSeek Harness 的这个问题在哪个版本修好了,排查时看哪几个提交?

DeepSeek Harness 在 master 上的提交 967ba4c4b8 把初始化从 stty -echo 改成 stty -echo; set +H,但它是 master 的祖先、不是 rc.3 tag 的祖先,所以 rc.3 仍会复现。

今天还在 rc.3 上,怎么绕过 DeepSeek Harness 这个带 ! 命令的卡死?

在 DeepSeek Harness 里把 ! 放进单引号并确保整条命令经过单引号引用,或直接用不含 ! 的等价写法(比如用 printf 生成字面量)。另外把长命令拆短,能让失败更快暴露。

相关术语

history expansion
history expansion 是 bash 在读取行时执行的历史展开机制,把 ! 开头的内容替换为历史事件。它在解析与执行之前发生,因此行内关闭命令来不及生效。— https://github.com/deepseek-ai/deepseek-harness/discussions/10636
quoteForBash
quoteForBash 是工具层把命令包装成可投递字符串的函数,使用 ANSI-C 引用 $'...'。它转义了反斜杠、单引号与换行,但没有处理感叹号。— https://github.com/deepseek-ai/deepseek-harness/discussions/10636
OSC 133;D
OSC 133;D 是终端完成检测协议中由 PROMPT_COMMAND 打印的标记,表示一条命令结束。丢弃行后 bash 重打的提示符不带它,完成检测因此失效。— https://github.com/deepseek-ai/deepseek-harness/discussions/10636
inferred_idle
inferred_idle 是完成检测的兜底推断状态,表示看起来空闲但没有拿到完成标记。当前工具循环不把它当作早退条件,于是会一直等到超时。— https://github.com/deepseek-ai/deepseek-harness/discussions/10636

来源