Skip to content

fix(command-mode): drain process output concurrently to prevent TerminalService pipe deadlock#430

Open
postoso wants to merge 1 commit into
altic-dev:mainfrom
postoso:fix/terminal-pipe-drain
Open

fix(command-mode): drain process output concurrently to prevent TerminalService pipe deadlock#430
postoso wants to merge 1 commit into
altic-dev:mainfrom
postoso:fix/terminal-pipe-drain

Conversation

@postoso

@postoso postoso commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

What

TerminalService.execute() (used by Command Mode to run shell commands) called process.waitUntilExit() BEFORE draining stdout/stderr. A command that writes more than the ~64KB pipe buffer blocks on write() once the pipe fills, while the parent blocks in waitUntilExit() — a classic pipe deadlock. The 30s timeout then fires terminate(), so high-output commands (seq, cat of a large file, git log, ls -R) spuriously hang ~30s and return truncated output with success=false.

Repro of the old logic against seq 1 200000: output truncated at exactly 65536 bytes, success=false, hung for the full timeout. After the fix: the complete 1288894 bytes, success=true, in ~68ms.

How

Drain stdout and stderr concurrently (each on a detached read task) started before waitUntilExit(), then await both and read termination status. Timeout behavior, return shape, and output trimming are unchanged.

Tests

Two methods added to DictationE2ETests (large stdout and large stderr): assert success, complete final line, output exceeding the 64KB buffer, exact line count, and prompt return (not the ~30s timeout). xcodebuild test green, swiftlint --strict clean.

@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: 3101ebd992

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +119 to +120
async let pendingOutput = Task.detached { outputHandle.readDataToEndOfFile() }.value
async let pendingError = Task.detached { errorHandle.readDataToEndOfFile() }.value

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Limit captured terminal output while draining pipes

When Command Mode runs a command that produces unbounded or very large output, such as yes or a verbose build that runs until the 30s timeout, these detached reads now keep the pipes drained while accumulating the entire stream in Data before returning. That removes the previous OS-pipe-buffer cap, so the app can allocate hundreds of MB/GB and be killed before the timeout; please keep draining but impose a maximum captured byte count/truncation policy.

Useful? React with 👍 / 👎.

@postoso

postoso commented Jun 26, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@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: 3101ebd992

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +119 to +120
async let pendingOutput = Task.detached { outputHandle.readDataToEndOfFile() }.value
async let pendingError = Task.detached { errorHandle.readDataToEndOfFile() }.value

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Cap terminal output while draining pipes

When Command Mode runs a command that produces unbounded or very high-volume output, such as yes, tail -f, or dumping a large file, these detached readDataToEndOfFile() calls now keep draining and accumulating the entire stream in memory until the process exits or the 30s timeout kills it. Removing the pipe backpressure fixes the 64KB deadlock, but without a maximum retained byte count this can consume large amounts of app memory and then append/send an enormous JSON tool result; consider draining while retaining only a bounded prefix/suffix or terminating once a limit is exceeded.

Useful? React with 👍 / 👎.

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