fix(ai/agent): pager 包裹不再吞掉 heredoc 结束符导致 agent 卡死 - #326
Open
Chiiz0 wants to merge 1 commit into
Open
Conversation
`turn_off_pager_for_command` 用裸文本拼接把闭合 token 粘到命令最后一个字符后面。 命令最后一行是 heredoc 结束符时(agent 写文件的常见写法 `python3 - <<'PY' … PY`), 拼出来是 `PY)` —— heredoc 结束符要求独占一行,shell 于是永远停在 PS2 续行提示符上 等那一行 `PY`。 后果:命令永不结束 → precmd 不触发 → block 停在非 finished 状态, `ActionResultDelay::UntilCompletion` 一路挂到 `MAX_UNTIL_COMPLETION_DURATION`(30 分钟), 期间 `is_active_and_long_running()` 守卫把 agent 后续所有命令打成 `CancelledBeforeExecution`,表现为整个 agent 会话卡死。Ctrl-C 救不回来:此时 shell 在读 续行输入,`^C` 只取消当前输入行,`(` 仍未闭合 —— 输出尾部的 `^C` 就是这个 bug 的指纹。 以尾随 `#` 注释结尾的命令是同一类破绽(闭合 token 被注释掉)。 修复:让闭合 token(`)` / `end` / `}`)独占一行,四个 shell 分支都改。 只在命令本身已经是多行时才切多行形态。单行命令保持原有单行字节形态 —— 在不支持 bracketed paste 的 shell(如 macOS 自带 bash 3.2)上,`bytes_to_execute_command` 会把 `\n` 换成 `\r`,给单行命令加换行会把一条命令拆成多个 block,而 agent mode 假定一条命令 一个 block。多行命令在那些 shell 上本来就已经被拆分,本改动不会让情况变差。 装饰逻辑从 `&self` 方法抽成自由函数 `wrap_command_without_pager` 以便单测。 已知未修:单行命令以尾随 `#` 注释结尾时仍会吞掉闭合 token。修它需要让单行命令也走多行 形态,会把上述 bracketed paste 拆块风险扩散到所有被装饰的命令,取舍上不划算;且该组合 在实际 agent 输出中基本不出现。 先在 #7 验证合入,现回流到本仓库。 Co-authored-by: Chiiz0 <248785316+Chiiz0@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
jwp2987
pushed a commit
to jwp2987/phosphor
that referenced
this pull request
Jul 29, 2026
turn_off_pager_for_command's no-pager wrapper appended its suffix directly after the command on one line. For multi-line commands ending in a heredoc (python3 <<'PY' ... PY), that glued the closing delimiter to the pager suffix (e.g. "PY)"), which bash no longer recognizes as the terminator — the shell hangs waiting for a delimiter that will never arrive. Replace the inline per-shell match with wrap_command_without_pager, which detects multi-line commands and puts the closing token (`)` / `end` / `}`) on its own line so the heredoc delimiter stays intact. Ports upstream zerx-lab#326. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
修复 Agent 执行含 heredoc 的多行命令时整个会话卡死的问题。
turn_off_pager_for_command用裸文本拼接把闭合 token 粘到命令最后一个字符后面。命令最后一行是 heredoc 结束符时(agent 写/改文件的常见写法),拼出来是这样:heredoc 结束符只有独占一行时才被识别。
PY)不匹配,shell 于是永远停在 PS2 续行提示符上等那一行PY。连锁反应:
ActionResultDelay::UntilCompletion一路挂到MAX_UNTIL_COMPLETION_DURATION(30 分钟)active_block().is_active_and_long_running()恒为真,RequestCommandOutput全部短路成CancelledBeforeExecution用户看到的就是整个 agent 会话死掉。Ctrl-C 救不回来——此时 shell 在读续行输入,
^C只取消当前输入行,(仍未闭合。输出最后一行是^C就是这个 bug 的指纹。不止 heredoc:命令最后一行以
#注释结尾时闭合 token 被注释掉,属于同一类破绽。四个 shell 分支(bash/zsh 的)、fish 的end、pwsh 的})形状相同,PowerShell here-string(@'…'@)也有同样的行位置要求。改动
让闭合 token 独占一行,只对本身已经是多行的命令:
单行路径刻意保持字节级不变。把单行命令变成两行,在不支持 bracketed paste 的 shell(如 macOS 自带 bash 3.2)上会被
bytes_to_execute_command的\n→\r替换拆成多个 block,而 agent mode 假定一条命令一个 block。多行命令在那些 shell 上本来就已经被拆分,本改动不会让情况变差。装饰逻辑从
&self方法抽成自由函数wrap_command_without_pager(Option<ShellType>, &str)以便单测,原方法退化为一行委托。已知未修
单行命令以
#注释结尾时(git log # 看看历史)仍会吞掉闭合 token,同样挂死。不修的理由:修它需要让单行命令也走多行形态,会把上面那个 bracketed paste 拆块的风险扩散到所有被装饰的命令、所有 shell 上——把一个罕见故障换成一个面很广的回归风险,取舍上不划算。
而且这个组合实际上基本不可达:装饰只在
wait_until_completion=true时触发,一条单行命令在这条路径上还要以尾随注释结尾,我们没见过。相比之下多行 heredoc 是 agent 写文件的常规写法,这次就是这么撞上的。上游情况
同一个 bug 在
warpdotdev/warpmaster 上同样存在(三个 shell 分支全中,只是触发条件更窄:要uses_pager=true且wait_until_completion=true)。已向 warp 提 issue:warpdotdev#14324本改动已先在
Infinimesh-ai/zap验证合入(Infinimesh-ai#7),现回流到本仓库。Testing
新增 4 个单测(
shell_command_tests.rs):multiline_heredoc_keeps_delimiter_and_closer_on_their_own_lines— heredoc 结束符与闭合括号各自独占一行,回归锁multiline_trailing_comment_does_not_swallow_closer— 覆盖 bash/fish/pwsh 三个分支single_line_command_stays_on_one_line— 钉死单行不换行unknown_shell_passes_command_through另外在 bash 5 上直接验证了 shell 层行为(脱离 Zap):
写进脚本时 EOF 会终止 heredoc,所以报语法错误;PTY 里 stdin 不会 EOF,所以真实症状是无限挂起而非报错。退出码透传也验证过(子壳里
sys.exit(7)→$? == 7),pager 抑制仍生效。ai::blocklist::action_model::execute::shell_command:10/10 通过cargo fmt --check:改动文件干净cargo nextest run --no-fail-fast -p warp,同一台机器上分别跑本仓库main(5d874456)和本分支:main基线main上的既有失败Server API dependencies
无。
Agent Mode
Changelog Entries for Stable
CHANGELOG-BUG-FIX: 修复 Agent 执行以 heredoc 结尾的多行命令时,shell 卡在续行提示符导致整个会话挂死的问题。