fix(ssh): keep Windows CLI when the launcher compiler is missing

The remote CLI installer deleted the legacy orca.cmd bridge before it
confirmed csc.exe existed, so a minimal Windows host without the .NET
compiler lost its existing CLI command and got nothing back.

Move the legacy-shim removal to run only after the compile and the
launcher-existence guard both pass. Missing-compiler and compile-failure
paths now exit non-zero while leaving the existing orca.cmd untouched; a
successful upgrade still clears the unsafe %* bridge (orca.exe already
shadows orca.cmd via PATHEXT). No %* forwarding is restored.

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Jinjing
2026-07-15 17:43:20 -07:00
co-authored by Orca
parent 24dc165b3c
commit d65091ef47
2 changed files with 74 additions and 9 deletions
+71 -6
View File
@@ -1,6 +1,6 @@
import { Buffer } from 'node:buffer'
import { spawnSync } from 'node:child_process'
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { dirname, join } from 'node:path'
import { describe, expect, it } from 'vitest'
@@ -23,15 +23,18 @@ function decodePowerShellCommand(command: string): string {
}
describe('SSH remote Orca CLI launcher', () => {
it('compiles a native Windows launcher without a cmd.exe argument bridge', () => {
const hostPlatform = getRemoteHostPlatform('win32-x64')
const plan = createRemoteCliInstallPlan({
function windowsInstallPlan(): ReturnType<typeof createRemoteCliInstallPlan> {
return createRemoteCliInstallPlan({
binDir: 'C:/Users/me user/.orca-relay/bin',
relayDir: 'C:/Users/me user/.orca-remote/relay-v1',
nodePath: 'C:/Program Files/nodejs/node.exe',
sockPath: '\\\\.\\pipe\\orca-relay-123',
hostPlatform
hostPlatform: getRemoteHostPlatform('win32-x64')
})
}
it('compiles a native Windows launcher without a cmd.exe argument bridge', () => {
const plan = windowsInstallPlan()
expect(plan.launcherPath).toBe('C:/Users/me user/.orca-relay/bin/orca.exe')
expect(plan.files).toHaveLength(1)
@@ -54,7 +57,24 @@ describe('SSH remote Orca CLI launcher', () => {
expect(compileScript).toContain('/out:orca.exe')
expect(compileScript).toContain('C:/Users/me user/.orca-relay/bin/orca-launcher.cs')
expect(compileScript).toContain('C:/Users/me user/.orca-relay/bin/orca.cmd')
expect(compileScript.indexOf('orca.cmd')).toBeLessThan(compileScript.indexOf('csc.exe'))
})
it('removes the legacy orca.cmd only after every compile guard has passed', () => {
const script = decodePowerShellCommand(windowsInstallPlan().postWriteCommands[0] ?? '')
const legacyShimRemoval =
"Remove-Item -LiteralPath 'C:/Users/me user/.orca-relay/bin/orca.cmd' -Force -ErrorAction SilentlyContinue"
// Why: a host missing csc.exe or failing the compile must keep its existing
// CLI, so every fail-closed guard precedes the legacy %* shim removal.
const guards = [
"if (-not $compiler) { Write-Error 'Unable to find the .NET Framework C# compiler required for the Orca SSH CLI launcher.'; exit 1 }",
'if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }',
"if (-not (Test-Path -LiteralPath 'C:/Users/me user/.orca-relay/bin/orca.exe' -PathType Leaf))"
]
expect(script).toContain(legacyShimRemoval)
for (const guard of guards) {
expect(script).toContain(guard)
expect(script.indexOf(guard)).toBeLessThan(script.indexOf(legacyShimRemoval))
}
})
itWindows('preserves a multiline argument through the compiled remote launcher', () => {
@@ -128,6 +148,51 @@ describe('SSH remote Orca CLI launcher', () => {
}
})
itWindows('preserves the existing orca.cmd when the compiler is missing', () => {
const root = mkdtempSync(join(tmpdir(), 'orca remote cli '))
try {
const binDir = join(root, 'bin').replaceAll('\\', '/')
mkdirSync(binDir, { recursive: true })
const legacyShimPath = join(binDir, 'orca.cmd')
writeFileSync(legacyShimPath, '@echo legacy orca cli\r\n', 'utf8')
const plan = createRemoteCliInstallPlan({
binDir,
relayDir: join(root, 'relay').replaceAll('\\', '/'),
nodePath: process.execPath,
sockPath: '\\\\.\\pipe\\orca-relay-test',
hostPlatform: getRemoteHostPlatform('win32-x64')
})
for (const file of plan.files) {
mkdirSync(dirname(file.path), { recursive: true })
writeFileSync(file.path, file.contents, 'utf8')
}
const encoded = plan.postWriteCommands[0]?.match(/-EncodedCommand\s+(\S+)/)?.[1]
expect(encoded).toBeTruthy()
// Point WINDIR at a directory with no csc.exe so compiler discovery fails.
const compile = spawnSync(
'powershell.exe',
[
'-NoProfile',
'-NonInteractive',
'-ExecutionPolicy',
'Bypass',
'-EncodedCommand',
encoded!
],
{ encoding: 'utf8', env: { ...process.env, WINDIR: root, SystemRoot: root } }
)
expect(compile.status).not.toBe(0)
expect(existsSync(legacyShimPath), 'existing orca.cmd must survive a failed install').toBe(
true
)
} finally {
rmSync(root, { recursive: true, force: true })
}
})
it('keeps the POSIX launcher as an argv-preserving shell exec', () => {
const plan = createRemoteCliInstallPlan({
binDir: '/home/me/.orca-relay/bin',
+3 -3
View File
@@ -169,9 +169,6 @@ function createWindowsLauncherCompileCommand(
.join(' ')
return powerShellCommand(
[
// Why: an upgrade must not leave the old %* batch bridge callable when
// compiler discovery fails; losing the convenience CLI is safer.
`Remove-Item -LiteralPath ${powerShellLiteral(legacyShimPath)} -Force -ErrorAction SilentlyContinue`,
`Set-Location -ErrorAction Stop -LiteralPath ${powerShellLiteral(binDir)}`,
'$windowsDirectory = if ($env:WINDIR) { $env:WINDIR } else { $env:SystemRoot }',
`$compilerCandidates = @((Join-Path $windowsDirectory 'Microsoft.NET\\Framework64\\v4.0.30319\\csc.exe'), (Join-Path $windowsDirectory 'Microsoft.NET\\Framework\\v4.0.30319\\csc.exe'))`,
@@ -180,6 +177,9 @@ function createWindowsLauncherCompileCommand(
`& $compiler ${compilerArgs}`,
'if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }',
`if (-not (Test-Path -LiteralPath ${powerShellLiteral(launcherPath)} -PathType Leaf)) { Write-Error 'The Orca SSH CLI launcher compiler produced no executable.'; exit 1 }`,
// Why: remove the legacy %* bridge only after a successful compile, so a
// host missing csc.exe keeps its existing CLI (orca.exe shadows orca.cmd).
`Remove-Item -LiteralPath ${powerShellLiteral(legacyShimPath)} -Force -ErrorAction SilentlyContinue`,
`Remove-Item -LiteralPath ${powerShellLiteral(sourcePath)} -Force`
].join('; ')
)