Skip to content

fix: restrict terminal childProcess.spawn to local-only environments#10538

Draft
shanevcantwell wants to merge 1 commit intocontinuedev:mainfrom
shanevcantwell:fix/remote-terminal-spawn
Draft

fix: restrict terminal childProcess.spawn to local-only environments#10538
shanevcantwell wants to merge 1 commit intocontinuedev:mainfrom
shanevcantwell:fix/remote-terminal-spawn

Conversation

@shanevcantwell
Copy link
Contributor

@shanevcantwell shanevcantwell commented Feb 15, 2026

Summary

  • childProcess.spawn() in runTerminalCommand executes on the extension host machine. With extensionKind: ["ui", "workspace"], the extension host almost always runs locally. For any remote workspace, spawning locally either runs commands on the wrong machine or fails with ENOENT.
  • Replace ENABLED_FOR_REMOTES (which listed all remote types as eligible for local spawn) with LOCAL_ONLY = ["", "local"]. All remote types now delegate to ide.runCommand(), which routes through VS Code's integrated terminal in the correct remote environment.
  • Remove the isWindowsHostWithRemote guard, which is now redundant — it was a Windows-specific patch for a platform-agnostic problem.

Fixes #10462

Note: VsCodeIde.runCommand() has pre-existing bugs that prevent it from working on remotes (sendText(command, false) never presses Enter, createTerminal() creates local terminals from UI-side extensions). These are tracked and fixed separately in #10542. This PR is intentionally scoped to only the runTerminalCommand.ts routing change.

Context

The ENABLED_FOR_REMOTES list was introduced in 1500281 (May 2025) to bring streaming output, background process support, and color to remote environments. The intent was sound — childProcess.spawn provides much better output capture than the ide.runCommand() fallback. However, the approach assumed the extension would be running on the remote side, when in practice extensionKind: ["ui", "workspace"] almost always places the extension host locally.

This created a structural contradiction: remote types were listed as "enabled" for local spawn, then a isWindowsHostWithRemote guard was added (PR #10391) to undo that for Windows hosts specifically. Non-Windows hosts (Linux→SSH, Mac→Dev Container, etc.) remained broken — commands either ran on the wrong machine silently or failed with spawn /bin/sh ENOENT as reported in #10462.

The correct fix is to recognize the actual invariant: childProcess.spawn() runs on the extension host, so it's only valid when there is no remote workspace. All remotes should delegate to ide.runCommand() regardless of host platform.

This does mean remote environments lose streaming output capture — ide.runCommand() executes the command correctly but returns no stdout/stderr. That's a real gap, but it's strictly better than ENOENT or running on the wrong machine. Improving output capture for remote terminals is tracked separately in #10542.

What changes

Host Remote Before After
Windows none/local spawn powershell no change
Windows any remote isWindowsHostWithRemoteide.runCommand() same path (guard now removed as redundant)
Linux/Mac none/local spawn shell no change
Linux/Mac ssh-remote spawn locally (ENOENT or wrong machine) ide.runCommand() (correct machine)
Linux/Mac dev-container spawn locally (wrong container) ide.runCommand() (correct container)
Linux/Mac codespaces/tunnel spawn locally (wrong machine) ide.runCommand() (correct machine)

No regressions. Every scenario either stays the same or gets fixed.

Test plan

  • cd core && npx vitest run — 1649 tests pass, 0 failures
  • Terminal command tests updated: WSL, dev-container, and unknown-remote tests now assert ide.runCommand() delegation
  • Manual verification with SSH remote (reporter environment)

childProcess.spawn executes on the extension host machine. With
extensionKind: ["ui", "workspace"], the extension host almost always
runs locally. For any remote workspace (SSH, WSL, Dev Container,
Codespaces, tunnel), spawning locally either runs commands on the wrong
machine or fails with ENOENT when the local shell doesn't exist in the
extension host context.

Replace ENABLED_FOR_REMOTES (which listed all remote types) with
LOCAL_ONLY (just "" and "local"). All remote types now fall through to
ide.runCommand() which delegates to VS Code's integrated terminal and
executes in the correct remote environment.

The isWindowsHostWithRemote guard is removed as it is now redundant —
the platform-agnostic check handles all cases.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@shanevcantwell shanevcantwell requested a review from a team as a code owner February 15, 2026 19:05
@shanevcantwell shanevcantwell requested review from RomneyDa and removed request for a team February 15, 2026 19:05
@dosubot dosubot bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Feb 15, 2026
Copy link
Contributor

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

Choose a reason for hiding this comment

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

No issues found across 2 files

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

Status: Todo

Development

Successfully merging this pull request may close these issues.

run_terminal_command fails with "spawn powershell.exe ENOENT" via SSH Remote from Windows

1 participant