Skip to content

fix(terminal): support Windows shell selection for SSH hosts#6466

Merged
Jinwoo-H merged 6 commits into
stablyai:mainfrom
rodboev:pr/support-windows-shell-selection-ssh-hosts
Jun 28, 2026
Merged

fix(terminal): support Windows shell selection for SSH hosts#6466
Jinwoo-H merged 6 commits into
stablyai:mainfrom
rodboev:pr/support-windows-shell-selection-ssh-hosts

Conversation

@rodboev

@rodboev rodboev commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Windows SSH worktrees now show host-scoped Windows shell rows in the new terminal menu when the remote host supports them.
  • Shell selection now flows through SSH preflight, the PTY provider, and the relay, so the chosen Windows shell launches on the remote host instead of falling back to the default shell.
  • The remote capability and cache path stays keyed by connectionId, so non-Windows SSH hosts and unrelated connections keep existing behavior.

Testing

  • pnpm lint
  • pnpm typecheck
  • pnpm test
  • pnpm build
  • Added or updated high-quality tests that would catch regressions, or explained why tests were not needed

Focused local validation:

  • pnpm exec vitest run --config config/vitest.config.ts src/relay/pty-handler.test.ts src/main/ipc/preflight.test.ts src/main/runtime/rpc/methods/preflight.test.ts src/main/providers/ssh-pty-provider.test.ts src/relay/preflight-handler.test.ts
  • pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/tab-bar/windows-shell-menu-visibility.test.ts src/renderer/src/components/tab-bar/TabBar.windows-shell-launch.test.ts src/renderer/src/lib/windows-terminal-capabilities.test.ts
  • pnpm run typecheck:web
  • pnpm run typecheck:node

AI Review Report

  • Cross-provider review on the initial branch state flagged a missing runtime RPC registration for preflight.detectRemoteWindowsTerminalCapabilities; that method and its dispatcher test were added.
  • Same-family review then flagged SSH Windows Git Bash forwarding the git-bash sentinel into relay spawn; the relay now resolves that sentinel to the remote bash.exe path and covers it with a regression test.
  • The final rerun reviewed renderer, main, preload, runtime RPC, and relay call paths, plus macOS, Linux, and Windows host-platform gating, shell labels, path resolution, and IPC vocabulary, and found no remaining issues.

Security Audit

  • Reviewed IPC and runtime RPC additions to confirm the new methods keep the existing typed connectionId contract and do not widen into arbitrary command execution.
  • Reviewed shell selection plumbing to confirm SSH still accepts only Orca's built-in Windows shell family, with relay-side Git Bash resolution using remote host paths instead of local filesystem guesses.
  • Reviewed cache and host-capability separation to confirm Windows shell availability stays scoped per SSH connection and does not leak across hosts or runtime targets.

Notes

@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR extracts shared preflight command execution helpers, adds remote Windows Terminal capability plumbing through main IPC, runtime RPC, preload, web, and relay layers, forwards SSH PTY shell override fields, updates renderer capability loading and tab bar behavior to use SSH connection state and remote host platform data, and localizes workspace cleanup UI strings and fixtures.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Several workspace-cleanup i18n and presentation changes are unrelated to SSH Windows shell selection and appear out of scope. Move the workspace-cleanup localization/presentation edits into a separate PR or explain how they are required by #5327.
Docstring Coverage ⚠️ Warning Docstring coverage is 3.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main SSH Windows shell-selection change.
Description check ✅ Passed The description covers summary, testing, AI review, security audit, and notes, matching the required template overall.
Linked Issues check ✅ Passed The changes implement SSH-host Windows shell capability detection and shell forwarding, matching the #5327 acceptance criteria.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🧹 Nitpick comments (3)
src/main/ipc/preflight-remote-windows-terminal-capabilities.ts (1)

19-30: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Returning a shared mutable default could leak mutations.

EMPTY_REMOTE_WINDOWS_TERMINAL_CAPABILITIES (including its wslDistros: [] array) is returned by reference on both the disposed and missing-result paths. Any downstream consumer that mutates wslDistros (e.g. .push) would corrupt the shared constant for every future empty result. Consider returning a fresh object/array.

♻️ Proposed fix
-const EMPTY_REMOTE_WINDOWS_TERMINAL_CAPABILITIES: RemoteWindowsTerminalCapabilities = {
-  wslAvailable: false,
-  wslDistros: [],
-  pwshAvailable: false,
-  gitBashAvailable: false,
-  hostPlatform: null
-}
+function emptyRemoteWindowsTerminalCapabilities(): RemoteWindowsTerminalCapabilities {
+  return {
+    wslAvailable: false,
+    wslDistros: [],
+    pwshAvailable: false,
+    gitBashAvailable: false,
+    hostPlatform: null
+  }
+}

(and return emptyRemoteWindowsTerminalCapabilities() at both sites)

src/relay/preflight-handler.ts (1)

70-75: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Defensive .catch() does not protect against synchronous throws.

isWslAvailable, isPwshAvailable, isGitBashAvailable, and listWslDistros are synchronous functions. In Promise.resolve(isWslAvailable()).catch(() => false), isWslAvailable() is evaluated synchronously before Promise.resolve, so a synchronous throw bypasses .catch, rejects Promise.all, and fails the whole handler. Today these helpers swallow errors internally so it's latent, but the guard doesn't deliver its intended fallback. Wrapping each call in an async thunk makes the .catch effective.

🛡️ Proposed fix
-    const [wslAvailable, pwshAvailable, gitBashAvailable] = await Promise.all([
-      Promise.resolve(isWslAvailable()).catch(() => false),
-      Promise.resolve(isPwshAvailable()).catch(() => false),
-      Promise.resolve(isGitBashAvailable()).catch(() => false)
-    ])
-    const wslDistros = wslAvailable ? await Promise.resolve(listWslDistros()).catch(() => []) : []
+    const [wslAvailable, pwshAvailable, gitBashAvailable] = await Promise.all([
+      (async () => isWslAvailable())().catch(() => false),
+      (async () => isPwshAvailable())().catch(() => false),
+      (async () => isGitBashAvailable())().catch(() => false)
+    ])
+    const wslDistros = wslAvailable
+      ? await (async () => listWslDistros())().catch(() => [])
+      : []
src/renderer/src/components/tab-bar/windows-shell-menu-visibility.ts (1)

11-14: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a short why-comment for the SSH-only branch.

The new early return is the non-obvious policy change here, but the existing note only explains runtime-host gating. A one-line comment above Line 11 would clarify that SSH worktrees must use the remote host platform and ignore the client OS.
As per coding guidelines, "**/*.{ts,tsx,js,jsx}: When writing or modifying code driven by a design doc or non-obvious constraint, add a comment explaining why the code behaves the way it does.`"

Source: Coding guidelines


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 51be9d1a-9738-4f54-9391-6a06cba1c39a

📥 Commits

Reviewing files that changed from the base of the PR and between 5fd9f0c and 45e859e.

📒 Files selected for processing (21)
  • src/main/ipc/preflight-command-exec.ts
  • src/main/ipc/preflight-remote-windows-terminal-capabilities.ts
  • src/main/ipc/preflight.test.ts
  • src/main/ipc/preflight.ts
  • src/main/providers/ssh-pty-provider.test.ts
  • src/main/providers/ssh-pty-provider.ts
  • src/main/runtime/rpc/methods/preflight.test.ts
  • src/main/runtime/rpc/methods/preflight.ts
  • src/preload/api-types.ts
  • src/preload/index.ts
  • src/relay/preflight-handler.test.ts
  • src/relay/preflight-handler.ts
  • src/relay/pty-handler.test.ts
  • src/relay/pty-handler.ts
  • src/renderer/src/components/tab-bar/TabBar.tsx
  • src/renderer/src/components/tab-bar/TabBar.windows-shell-launch.test.ts
  • src/renderer/src/components/tab-bar/windows-shell-menu-visibility.test.ts
  • src/renderer/src/components/tab-bar/windows-shell-menu-visibility.ts
  • src/renderer/src/lib/windows-terminal-capabilities.test.ts
  • src/renderer/src/lib/windows-terminal-capabilities.ts
  • src/renderer/src/web/web-preload-api.ts

Comment thread src/relay/pty-handler.ts
Comment thread src/renderer/src/lib/windows-terminal-capabilities.ts

@Jinwoo-H Jinwoo-H 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.

Thanks, this is in good shape now. I pushed review fixes to preserve Windows shell selection only for Windows SSH hosts, keep non-Windows SSH relays from receiving Windows-only shell overrides, and resolve the current main merge conflict.\n\nValidated with typecheck, lint, focused Vitest coverage, Electron/CDP UI state checks for Windows/Linux SSH shell menu behavior, and a real Docker SSH relay/PTY run.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 7e235ca6-0509-4820-a813-06a0852ecb68

📥 Commits

Reviewing files that changed from the base of the PR and between 91ddd6f and f99cd36.

📒 Files selected for processing (13)
  • src/main/providers/ssh-pty-provider.test.ts
  • src/main/providers/ssh-pty-provider.ts
  • src/preload/api-types.ts
  • src/preload/index.ts
  • src/relay/pty-handler.test.ts
  • src/relay/pty-handler.ts
  • src/renderer/src/components/workspace-cleanup/WorkspaceCleanupDialog.tsx
  • src/renderer/src/components/workspace-cleanup/workspace-cleanup-presentation-fixtures.ts
  • src/renderer/src/components/workspace-cleanup/workspace-cleanup-presentation.ts
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/i18n/locales/es.json
  • src/renderer/src/i18n/locales/ja.json
  • src/renderer/src/i18n/locales/ko.json
💤 Files with no reviewable changes (7)
  • src/renderer/src/components/workspace-cleanup/workspace-cleanup-presentation-fixtures.ts
  • src/renderer/src/components/workspace-cleanup/workspace-cleanup-presentation.ts
  • src/renderer/src/components/workspace-cleanup/WorkspaceCleanupDialog.tsx
  • src/renderer/src/i18n/locales/ko.json
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/i18n/locales/ja.json
  • src/renderer/src/i18n/locales/es.json
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/preload/index.ts
  • src/main/providers/ssh-pty-provider.test.ts
  • src/main/providers/ssh-pty-provider.ts
  • src/preload/api-types.ts

Comment thread src/relay/pty-handler.ts
Comment on lines +145 to +156
function resolvePtyShellOverride(shellOverride: string): string {
if (!shellOverride) {
return ''
}
if (process.platform !== 'win32') {
return ''
}
const normalized = shellOverride.toLowerCase()
if (!ALLOWED_WINDOWS_SHELL_OVERRIDES.has(normalized)) {
throw new Error(`Unsupported Windows shell override: ${shellOverride}`)
}
return resolveWindowsGitBashShellPath(shellOverride) ?? shellOverride

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Let the Git Bash resolver run before the named-shell allowlist.

resolveWindowsGitBashShellPath() already accepts the canonical git-bash token, bare bash.exe, and validated Git-for-Windows paths, but the new allowlist rejects the latter two before they ever reach that resolver. That means a valid Git Bash override can now throw Unsupported Windows shell override instead of being normalized and launched.

Suggested fix
 function resolvePtyShellOverride(shellOverride: string): string {
   if (!shellOverride) {
     return ''
   }
   if (process.platform !== 'win32') {
     return ''
   }
+  const resolvedGitBash = resolveWindowsGitBashShellPath(shellOverride)
+  if (resolvedGitBash) {
+    return resolvedGitBash
+  }
   const normalized = shellOverride.toLowerCase()
   if (!ALLOWED_WINDOWS_SHELL_OVERRIDES.has(normalized)) {
     throw new Error(`Unsupported Windows shell override: ${shellOverride}`)
   }
-  return resolveWindowsGitBashShellPath(shellOverride) ?? shellOverride
+  return shellOverride
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function resolvePtyShellOverride(shellOverride: string): string {
if (!shellOverride) {
return ''
}
if (process.platform !== 'win32') {
return ''
}
const normalized = shellOverride.toLowerCase()
if (!ALLOWED_WINDOWS_SHELL_OVERRIDES.has(normalized)) {
throw new Error(`Unsupported Windows shell override: ${shellOverride}`)
}
return resolveWindowsGitBashShellPath(shellOverride) ?? shellOverride
function resolvePtyShellOverride(shellOverride: string): string {
if (!shellOverride) {
return ''
}
if (process.platform !== 'win32') {
return ''
}
const resolvedGitBash = resolveWindowsGitBashShellPath(shellOverride)
if (resolvedGitBash) {
return resolvedGitBash
}
const normalized = shellOverride.toLowerCase()
if (!ALLOWED_WINDOWS_SHELL_OVERRIDES.has(normalized)) {
throw new Error(`Unsupported Windows shell override: ${shellOverride}`)
}
return shellOverride
}

@Jinwoo-H Jinwoo-H merged commit 5e3d8a1 into stablyai:main Jun 28, 2026
1 check passed
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.

Support Windows shell selection for SSH hosts

2 participants