Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Add CommandLineMultilineWrapRewriter to handle multi-line POSIX commands#312263

Closed
meganrogge wants to merge 1 commit intomainfrom
merogge/multiline-wrap
Closed

Add CommandLineMultilineWrapRewriter to handle multi-line POSIX commands#312263
meganrogge wants to merge 1 commit intomainfrom
merogge/multiline-wrap

Conversation

@meganrogge
Copy link
Copy Markdown
Collaborator

Fixes #312260.

Problem

RichExecuteStrategy.execute() resolves on the first onCommandFinished shell-integration marker:

// richExecuteStrategy.ts
Event.toPromise(this._commandDetection.onCommandFinished, store).then(...)

When the chat agent pastes a multi-line POSIX command, shell integration fires one marker per top-level statement. The tool returns partial/empty output after line 1 while the rest of the command keeps running unattended.

Fix

New CommandLineMultilineWrapRewriter that wraps multi-line POSIX commands in <shell> -c '...' before execution, so shell integration sees exactly one command end marker. Skips Windows/PowerShell and line-continuations (\<LF>). Not gated on detachBackgroundProcesses — the race affects foreground commands too. OutputMonitor input detection is unaffected (it reads the PTY directly).

Proof — eval run 24857589683, terminalbench2, vscode agent, gpt-5.4

Affected failing instances whose first tool call was a multi-line heredoc that returned early (patch=0B after wasted follow-up turns):

Test systemInitiatedLabel (first line of submission)
bn-fit-modify cd /app && python3 - <<'PY'
distribution-search cd /app && python - <<'PY'
gcode-to-text cd /app && python - <<'PY'
protein-assembly python - <<'PY'
raman-fitting cd /app && python - <<'PY'
rstan-to-pystan cd /app && .venv/bin/python - <<'PY'
regex-chess python - <<'PY'
schemelike-metacircular-eval pushd /app >/dev/null && python3 interp.py eval.scm <<'EOF'

All 8 produced 0-byte patches after the first run_in_terminal call returned before the heredoc body finished. Logs under .vscode-eval/24857589683/<test>/output/vsc-output/chat-turns/chat-export-step-0.json.

Tests

commandLineMultilineWrapRewriter.test.ts — 8/8 green:

✔ single-line bash command is not rewritten
✔ escaped newline (line continuation) is not treated as multi-line
✔ Windows is skipped
✔ PowerShell on POSIX is skipped
✔ bash: multi-line command is wrapped in <shell> -c
✔ zsh: multi-line command is wrapped
✔ bash: single quotes in the command are escaped
✔ fish: multi-line command is wrapped with double-quote escaping

RichExecuteStrategy resolves on the first onCommandFinished shell
integration marker. When the agent pastes a multi-line POSIX command
(e.g. a python/node heredoc or a set -e; apt-get update script),
shell integration emits one marker per top-level statement and the
strategy returns partial or empty output after the first line while
the rest of the command keeps running unattended.

Wrap multi-line POSIX commands in <shell> -c '...' so shell integration
sees a single command end marker. Skipped for PowerShell/Windows and
for commands whose newlines are line-continuation (preceded by \).
Input detection via OutputMonitor is unaffected because it reads PTY
output independently of shell integration markers, so interactive
prompts still surface for both foreground and background terminals.
Copilot AI review requested due to automatic review settings April 23, 2026 23:37
@meganrogge meganrogge self-assigned this Apr 23, 2026
@meganrogge meganrogge added this to the 1.118.0 milestone Apr 23, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes run_in_terminal returning early for multi-line POSIX submissions by introducing a command-line rewriter that wraps multi-line commands in <shell> -c ..., ensuring shell integration emits a single onCommandFinished marker for the whole tool call.

Changes:

  • Added CommandLineMultilineWrapRewriter to wrap multi-line POSIX commands (with shell-specific quoting for fish vs bash/zsh/sh).
  • Registered the new rewriter early in RunInTerminalTool’s rewriter chain to run before background detaching.
  • Added unit tests covering the new rewriting behavior and escaping rules.
Show a summary per file
File Description
src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineRewriter/commandLineMultilineWrapRewriter.ts Implements multi-line detection and wrapping via <shell> -c ... with shell-specific escaping.
src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts Registers the new multiline rewriter in the command-line rewriter pipeline.
src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/commandLineMultilineWrapRewriter.test.ts Adds tests to validate wrapping/skip logic and escaping behavior across shells/OSes.

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 2

Comment on lines +35 to +40
// Detect a "real" newline that separates top-level statements. We require a bare LF
// that is NOT line-continuation (preceded by `\`). A single-line command with escaped
// newlines continues to be a single command; we must not wrap it.
if (!/(^|[^\\])\n\s*\S/.test(command)) {
return undefined;
}
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

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

The multi-line detection regex treats any newline preceded by a backslash as a line continuation. This is incorrect when the newline is preceded by an even number of backslashes (e.g. \\\n): in POSIX shells that newline is not continued, so the command is truly multi-line and should be wrapped. Consider switching to logic that checks for an odd number of trailing backslashes before the newline (and ideally supports \r?\n).

Copilot uses AI. Check for mistakes.
Comment on lines +42 to +57
if (isFish(options.shell, options.os)) {
// Fish does not support the POSIX `'\''` escape inside single-quoted strings.
// Use double quotes and escape backslash and double-quote.
const escaped = command.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
return {
rewritten: `${options.shell} -c "${escaped}"`,
reasoning: 'Wrapped multi-line command with `fish -c` so shell integration sees a single command',
forDisplay: command,
};
}

// bash/zsh: escape single quotes using the standard `'\''` sequence so the entire
// command can live inside a single-quoted `-c` argument without further interpretation.
const escaped = command.replace(/'/g, `'\\''`);
return {
rewritten: `${options.shell} -c '${escaped}'`,
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

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

This file duplicates the same fish vs bash/zsh -c quoting/escaping logic that already exists in CommandLineBackgroundDetachRewriter. To avoid the two implementations drifting (and to make it easier to add support for additional shells/escaping tweaks), consider extracting a small shared helper for building a safe <shell> -c <script> invocation.

Copilot uses AI. Check for mistakes.
@meganrogge
Copy link
Copy Markdown
Collaborator Author

this is a bad approach

@meganrogge meganrogge closed this Apr 23, 2026
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.

run_in_terminal returns partial output for multi-line POSIX commands (RichExecuteStrategy resolves on first onCommandFinished)

2 participants