Skip to content

fix(agent): detect shell completion marker on output without trailing newline#4744

Open
logicwu0 wants to merge 1 commit into
alibaba:mainfrom
logicwu0:fix/4740-shell-marker-no-trailing-newline
Open

fix(agent): detect shell completion marker on output without trailing newline#4744
logicwu0 wants to merge 1 commit into
alibaba:mainfrom
logicwu0:fix/4740-shell-marker-no-trailing-newline

Conversation

@logicwu0

Copy link
Copy Markdown
Contributor

What this PR does

Fixes #4740: ShellSessionManager hangs when a shell command's output has no trailing newline.

Root cause

ShellSession#collectOutput detects command completion by checking whether a stdout line startsWith the marker that is echoed after each command (echo <marker> $?). The stdout reader splits on BufferedReader.readLine(), so when the user command's output does not end with a newline (e.g. cat of a file without a final newline, or printf 'x'), that trailing output and the marker are read as a single line:

<file content>__LC_SHELL_DONE__<uuid> 0

startsWith(marker) is then false, the marker is never matched, and outputQueue.poll blocks until the command times out and the session is restarted — observed as a hang.

Fix

Detect the marker anywhere in the line via indexOf:

  1. locate the marker in the line to recognize completion;
  2. keep any real output that precedes the marker (so file content isn't lost);
  3. parse the exit code from the part after the marker.

The marker is __LC_SHELL_DONE__ + a random UUID, so an in-line search won't false-match normal output.

Tests

Added ShellSessionManagerTest (no API key required):

  • printf 'hello-no-newline' → output captured, no timeout;
  • printf 'oops' && false → exit code still parsed from the merged line;
  • normal trailing-newline output regression.

Verified before the fix the first two time out (reproducing the hang); after the fix all pass.

@logicwu0 logicwu0 changed the title fix(agent-framework): detect shell completion marker on output without trailing newline fix(agent): detect shell completion marker on output without trailing newline Jun 23, 2026
@logicwu0 logicwu0 force-pushed the fix/4740-shell-marker-no-trailing-newline branch from 1de8219 to 1132247 Compare June 24, 2026 00:37

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1132247329

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +399 to +400
int markerIndex = line.indexOf(marker);
if (markerIndex >= 0) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Only complete after a valid marker exit code

When a user command reads from stdin (for example cat, head -n1, or read x; echo "$x"), it can consume and echo the injected marker command because the marker is written to the same shell stdin immediately after the user command. With the new indexOf check, the quoted marker inside that echoed command line is treated as completion even though the real marker command never ran, so exitCode stays null, the result is reported as successful, and long-running commands like cat can keep owning the session for subsequent executions. The previous startsWith behavior timed out and restarted in this case; this should only set completed after the suffix parses as the expected marker exit status.

Useful? React with 👍 / 👎.

@logicwu0

Copy link
Copy Markdown
Contributor Author

@chatgpt-codex-connector 这个问题确实存在,感谢指出,已在 7c20a8c 修复。

根因:marker 命令是紧接着用户命令写入同一个 stdin 的。当用户命令会读 stdin 时(如无参 catreadhead -n1),它会把注入的 marker 命令当成输入吃掉并回显出来。那行虽然包含 marker 子串,但 marker 后面跟的不是合法退出码,之前的 indexOf 判定会把它误当作完成 → exitCode 保持 null(被报告为成功)→ 真正的命令仍占用会话。

修复:只有当 marker 之后的文本能解析成预期的退出码整数时,才置 completed;否则继续读取(对读 stdin 的命令恢复为之前的超时/重启行为)。新增了一个回归测试:执行 cat 并断言它超时而不是被误判为成功完成。

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7c20a8cf2c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +408 to +409
Integer parsedExitCode = parseExitCode(line.substring(markerIndex + marker.length()));
if (parsedExitCode != null) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Accept PowerShell markers without $LASTEXITCODE

When the default Windows shell (powershell.exe) runs a cmdlet or script that has not set $LASTEXITCODE, the marker emitted as Write-Output "<marker> $LASTEXITCODE" has no integer suffix; Microsoft documents $LASTEXITCODE as the exit code of the last native program or script and notes scripts can leave it unchanged (https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_automatic_variables). Because this branch now refuses to mark completion unless parseExitCode returns an integer, commands like Get-Location in a fresh PowerShell session are treated as normal output and wait until timeout/restart, whereas the previous code completed with exitCode == null.

Useful? React with 👍 / 👎.

@logicwu0 logicwu0 force-pushed the fix/4740-shell-marker-no-trailing-newline branch from 7c20a8c to 0a81231 Compare July 5, 2026 14:47
… newline

ShellSessionManager.ShellSession#collectOutput detected the completion
marker with startsWith, but when a command's output has no trailing
newline (e.g. `cat` of a file without a final newline, or `printf`
without `\n`) the line reader merges that trailing output and the marker
onto one line, so the marker is never matched and collectOutput hangs
until the command times out.

Detect the marker anywhere in the line via indexOf, preserve any real
output preceding it, and only treat it as completion when a valid exit
code follows the marker. The latter guard is required because a
stdin-reading command (e.g. no-arg `cat`) echoes the injected marker
command itself before running; that echoed line contains the marker
substring but no trailing exit code and must not falsely complete.

Closes alibaba#4740
@logicwu0 logicwu0 force-pushed the fix/4740-shell-marker-no-trailing-newline branch from 0a81231 to 970cf32 Compare July 8, 2026 01:28
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.

[BUG] com.alibaba.cloud.ai.graph.agent.tools.ShellSessionManager执行Shell读取文件卡死

1 participant