Skip to content

Shell integration: executeCommand().read() stream and onDidEndTerminalShellExecution both permanently silent (zero data) for certain multi-line / subshell command shapes #324392

Open

Description

@edelauna

Title

Shell integration: executeCommand().read() stream and onDidEndTerminalShellExecution both permanently silent (zero data) for certain multi-line / subshell command shapes

Does this issue occur when all extensions are disabled?

Not tested directly (reproduced via an extension driving the shell-integration API), but the failure is in VS Code's own OSC-633 parsing/event delivery, not extension logic — see reproduction below, which is a minimal, extension-only repro path.

Environment

  • VS Code Version: 1.100.0 (also reported by users on 1.123–1.127, so not version-specific)
  • OS: Linux (Ubuntu 24.04), bash 5.2
  • Shell: bash, default profile, real (non-custom) shell integration script

Summary

When a command executed via TerminalShellIntegration.executeCommand() has certain shapes — specifically (a) a command string containing a literal embedded newline, or (b) a ( ... ) subshell — VS Code can permanently stop delivering any data on the execution.read() stream, and onDidEndTerminalShellExecution never fires either. The command itself completes normally and its output is visible in the terminal UI, but the extension-facing API surfaces zero data and no completion signal, indefinitely (observed for 100+ seconds; the underlying shell process has already exited by then).

This is a more severe variant of previously reported issues (#316556, #250764, #254724): those describe the end marker specifically being lost while some stream data still arrives; this report is about all stream data (including the start marker) being lost.

Steps to Reproduce

  1. Open a VS Code extension host with a workspace open, default bash profile, shell integration enabled.
  2. Programmatically create/get an integrated terminal and wait for terminal.shellIntegration to become available.
  3. Call terminal.shellIntegration.executeCommand(command) for either of:
    • A command containing a literal newline, wrapped in a { } group to keep it as one execution (needed to avoid VS Code splitting multi-line input into separate executions — see Terminal Shell Integration breaks multi-line commands in Powershell #267344-adjacent behavior):
      {
      python3 -c "
      import sys
      print('boom', file=sys.stderr)
      sys.exit(1)
      "
      }
      
    • Or a ( ... ) subshell, single line:
      ( python3 -c "import sys; print('boom', file=sys.stderr); sys.exit(1)" )
      
  4. Call execution.read() immediately (before or in the same tick as step 3, per VS Code's own guidance that output before the first read() call may be lost) and iterate the returned AsyncIterable<string> with for await.
  5. Also register vscode.window.onDidEndTerminalShellExecution before executing.

Expected Result

Either:

  • The stream yields the ]633;C start marker, the command's output, and the ]633;D end marker, and the stream closes; or
  • onDidEndTerminalShellExecution fires with the exit code.

Actual Result

Neither happens. The for await loop never receives a single chunk (not even the ]633;C start marker), and onDidEndTerminalShellExecution never fires. Confirmed via 4/4 consecutive runs of an automated e2e test (using @vscode/test-electron against this exact VS Code version) that the stream is silent for 100+ seconds while the underlying python3 process has already exited (verified via a separate ps check finding no matching process).

Only vscode.window.onDidStartTerminalShellExecution fires — and critically, for the ( ) subshell reproduction, its own reported command field is corrupted: the script's stderr output ("boom") appears concatenated directly into the value VS Code believes is the command line:

{
  command: '( python3 -c "import sys; print(\'boom\', file=sys.stderr); sys.exit(1)" )\nboom',
  terminalId: 1
}

This strongly suggests the internal OSC-633 sequence parser is misparsing command/output boundaries for these constructs, which likely also explains why no further data is delivered afterward (the parser state machine may be stuck expecting more command-line input rather than transitioning to output-capture mode).

Additional data point (single-line brace group works fine)

To isolate whether grouping syntax itself was the trigger, we tested a single-line { } group (no embedded newline): { python3 -c "import sys; print('boom', file=sys.stderr); sys.exit(1)"; }. This works correctly — one chunk delivered, stream closes normally, onDidEndTerminalShellExecution fires as expected (~11s, no signal loss). This isolates the trigger to either (a) an embedded literal newline within the group, or (b) subshell (( )) semantics specifically — not brace-grouping in general.

Impact

Any extension using the shell-integration API to track command completion (Copilot Chat's terminal tool, and third-party AI coding extensions like Cline/Roo Code forks) can hang indefinitely waiting for a signal that will never arrive, for any command a user or model happens to phrase as a multi-line script or a subshell. We've had to ship a defensive workaround (self-finalizing on the ]633;D marker text within accumulated stream data, when present) but this specific failure mode has no marker at all to detect, so no application-level workaround exists for it.

Related Issues

This report adds: (1) a case where zero data at all is delivered, not just a delayed/missing end marker, and (2) direct evidence of corrupted command-line parsing (the commandLine.value/command field containing command output) as a likely root cause.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions