Skip to content

feat: shell integration-based terminal output capture for remote environments - #10786

Open
shanevcantwell wants to merge 1 commit into
continuedev:mainfrom
shanevcantwell:fix/vscode-runcommand-remote
Open

shanevcantwell wants to merge 1 commit into
continuedev:mainfrom
shanevcantwell:fix/vscode-runcommand-remote

Conversation

@shanevcantwell

@shanevcantwell shanevcantwell commented Feb 24, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • When the extension host runs on Windows with a remote workspace (SSH, WSL, Dev Container), childProcess.spawn executes locally instead of on the remote. The existing fallback used ide.runCommand (sendText) with no output capture, returning a hardcoded "Command failed" status.
  • Adds a new runCommandWithOutput IDE method using VS Code's Shell Integration API (1.93+) to execute commands on remote terminals with full stdout capture and ANSI color preservation.
  • When shell integration is unavailable, runCommandWithOutput falls back to sendText internally and the caller reports "Command completed" — no double-execution.

How it works

  1. Creates an invisible terminal (not shown to user)
  2. Waits for shell integration to activate via onDidChangeTerminalShellIntegration (10s timeout)
  3. Executes via shellIntegration.executeCommand(command)
  4. Reads output via execution.read() async iterable
  5. Strips only VS Code's internal OSC 633 markers; preserves ANSI color codes for UnifiedTerminal's AnsiRenderer
  6. Disposes terminal after capture
  7. If shell integration is unavailable: shows terminal, sends command via sendText, returns empty string (command still executes, just without output capture)

Files changed (10)

File Change
core/index.d.ts Add runCommandWithOutput to IDE interface
core/config/types.ts Add to global IDE type
core/protocol/ide.ts Add protocol type
core/protocol/messenger/messageIde.ts Add proxy method
core/protocol/messenger/reverseMessageIde.ts Add listener
extensions/vscode/src/extension/VsCodeMessenger.ts Add handler
extensions/vscode/src/VsCodeIde.ts runCommandWithOutput() + waitForShellIntegration() implementation
core/tools/implementations/runTerminalCommand.ts Use new method in remote fallback path
core/tools/implementations/runTerminalCommand.vitest.ts Update mocks and assertions
core/util/filesystem.ts Add stub for FileSystemIde

Test plan

  • SSH remote: pwd, echo, Python scripts with ANSI output — green "Command completed" with captured output and rendered colors
  • WSL remote: Same commands — working, ~5s initial delay due to shell integration startup on WSL terminals
  • Native Windows (no regression): Local childProcess.spawn path completely unaffected — no changes to that code path
  • Shell integration unavailable: Falls back to sendText internally, reports "Command completed" without double-executing
  • Unit tests: 36/37 pass (1 pre-existing failure unrelated to this change)

Related issues

🤖 Generated with Claude Code

@shanevcantwell
shanevcantwell requested a review from a team as a code owner February 24, 2026 12:15
@shanevcantwell
shanevcantwell requested review from sestinj and removed request for a team February 24, 2026 12:15
@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Feb 24, 2026
@haystack-code-reviewer-pr-hook

Copy link
Copy Markdown

Try Haystack Code Reviewer

Want AI-powered code review for this PR? Get instant analysis, interactive visualizations, and actionable insights.

Review this PR with Haystack

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

1 issue found across 10 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="core/tools/implementations/runTerminalCommand.vitest.ts">

<violation number="1" location="core/tools/implementations/runTerminalCommand.vitest.ts:273">
P0: Fallback on empty output causes commands to execute twice in remote environments</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@shanevcantwell

Copy link
Copy Markdown
Contributor Author

Better now in ssh and wsl-Ubuntu remote. Containers, JetBrains, linux mint, macos use the same code path. Thanks for the patience while I got to the core of the issue.

@RomneyDa RomneyDa left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@shanevcantwell I think current implementation would break things for Jetbrains. You could add ide type detection or jetbrains support

@shanevcantwell

shanevcantwell commented Apr 28, 2026 •

Copy link
Copy Markdown
Contributor Author

@shanevcantwell I think current implementation would break things for Jetbrains. You could add ide type detection or jetbrains support

Hi Dallin - I completely missed your update here. I'm curious why you think Jetbrains won't go down the same code path as what #10538 fixed. I don't have $700+ to repro tho. I only had the preview subscription.

@shanevcantwell

shanevcantwell commented Apr 28, 2026 •

Copy link
Copy Markdown
Contributor Author

Analysis strictly from the code:

PR #10538 established IDE-agnostic routing — the decision is local vs remote, not IDE type. Both VS Code and JetBrains go through ide.runCommand(). No IDE type detection needed there.

PR #10786 adds a new protocol method (runCommandWithOutput). Each IDE implements it natively:

  • VS Code → Shell Integration API (this PR)
  • JetBrains → terminal widget system, similar to how they implemented runCommand() years ago

This is an implementation gap on the JetBrains side, not an architecture problem. The delegation pattern already works — each IDE provides its own native output capture. Adding it to IntelliJIDE.kt follows the same protocol-driven approach that has worked for all prior PRs (#9679, #9740, #10391, etc.).

@gesinn-it-gea

gesinn-it-gea commented Jun 5, 2026 •

Copy link
Copy Markdown

Would this fix #10542? I'm getting the following error: Command executed in remote terminal. Output capture is not yet available for remote environments. error in a WSL setup, too.

…environments

When the extension host runs on Windows with a remote workspace (SSH, WSL,
Dev Container), childProcess.spawn executes locally instead of on the remote.
The existing fallback used ide.runCommand (sendText) which had no output
capture, returning a hardcoded "Command failed" status.

This adds a new runCommandWithOutput IDE method that uses VS Code's Shell
Integration API (1.93+) to execute commands on remote terminals with full
output capture. The method creates an invisible terminal, waits for shell
integration to activate, executes via shellIntegration.executeCommand(),
and reads output via the async iterable read() API. ANSI color codes are
preserved for rendering by the UnifiedTerminal component; only VS Code's
internal OSC 633 shell integration markers are stripped.

Falls back gracefully to sendText when shell integration is unavailable.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@shanevcantwell
shanevcantwell force-pushed the fix/vscode-runcommand-remote branch from bccf81f to 1f577f2 Compare July 12, 2026 07:27
@shanevcantwell

Copy link
Copy Markdown
Contributor Author

Rebased onto latest main — conflicts cleared, diff is down to just the feature.

On the JetBrains question: this PR doesn't change JetBrains behavior. The routing from #10538 is IDE-agnostic (local vs. remote), and runCommandWithOutput is a new protocol method each IDE implements natively — VS Code via Shell Integration here. JetBrains would get its own native implementation in IntelliJIDE.kt, same pattern as runCommand() before it. That's additive follow-up work, not a regression this PR introduces.

Given #10542 now has confirmed repros across Remote SSH, WSL, and podman devcontainers, would it be reasonable to land the VS Code path and track JetBrains parity separately? Happy to file that as a follow-up issue.

@samrocketman

samrocketman commented Jul 28, 2026 •

Copy link
Copy Markdown

I'm trying to use this extension with Dev Containers to run models isolated from my host computer in an unprivileged docker container.

Unfortunately, Continue extension cannot operate in agent mode with tool calls within a docker vscode devcontainer...

@samrocketman

samrocketman commented Jul 28, 2026 •

Copy link
Copy Markdown

From a devcontainer I compiled this extension and asked my local AI to:

Run ls -1 /dev and tell me the output

The execution attempt failed with the following error.

$ ls -1 /dev

run_terminal_command failed with the message: spawn /bin/bash ENOENT

Please try something else or request further instructions.

Build info

  • commit 1f577f2
  • nvm install v20.19.0 && nvm use v20.19.0
  • ./scripts/install-dependencies.sh
  • Dragged build from ./extensions/vscode/build/continue-1.3.33.vsix

Environment

  • VSCode 1.130.0
  • Dev container extension version 0.466.0
  • Docker server 29.6.2

@brakubraku

Copy link
Copy Markdown

i have run into the same problem as samrocketman

@samrocketman

Copy link
Copy Markdown

laffertyryan0#10 looks promising

@nzzlinh

nzzlinh commented Oct 7, 2026

Copy link
Copy Markdown

I tried building this PR. It seems working good but still have an issue: the opened terminal sometimes requires user interaction. In my case, it ran a git diff and then waited for me to terminate the command. Besides that, it sometimes missed the output form remote terminal, not frequent.

@shanevcantwell

Copy link
Copy Markdown
Contributor Author

I tried building this PR. It seems working good but still have an issue: the opened terminal sometimes requires user interaction. In my case, it ran a git diff and then waited for me to terminate the command. Besides that, it sometimes missed the output form remote terminal, not frequent.

Let me know if you can track down any pattern to that. I dug around for race conditions when submitting this fix, but it sounds like there might be something buried in there still.

vibimvab added a commit to vibimvab/continue-2.0.0-remote-capture that referenced this pull request Oct 27, 2026
Port continuedev#10786 using shell execution completion events for exit status. Preserve fallback and background execution, add regression tests, and update extension version metadata.
@vibimvab

Copy link
Copy Markdown

I tried building this PR. It seems working good but still have an issue: the opened terminal sometimes requires user interaction. In my case, it ran a git diff and then waited for me to terminate the command. Besides that, it sometimes missed the output form remote terminal, not frequent.

Let me know if you can track down any pattern to that. I dug around for race conditions when submitting this fix, but it sounds like there might be something buried in there still.

I used Codex to port this PR to a Continue 2.0.0-based fork and tested it in a Linux SSH workspace, with the terminal visible during command execution.

There seems to be a race condition between command execution/output capture and Python environment auto-activation.

With the following setting, output capture always succeeded in my testing:
"python.terminal.activateEnvironment": false
When the setting was omitted or set to true, output capture failed most of the time. The command itself executed, but Continue kept waiting until the two-minute capture timeout.

Longer commands seem more likely to fail, although I’m not certain whether command length is actually a factor.

During failures, Python’s activation command appears to be injected alongside the requested command:

$ printf 'START\n'; sleep 3; printf 'END\n'
 source .venv/bin/activate
START
END
$  source .venv/bin/activate
(.venv) $

The same behavior also occurs with true:

$ true
 source .venv/bin/activate
$  source .venv/bin/activate
(.venv) $

Could Python’s asynchronous activation command interfere with shell integration’s execution tracking when both extensions send commands to a newly created terminal?

These observations are from my adapted port; I haven’t confirmed whether the same issue occurs with this PR on its original base.

This branch has not been deployed

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

Labels

size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

7 participants