fix: detect noisy Windows ARM64 SSH platforms (#7965)

* fix: detect noisy Windows ARM64 SSH platforms

- Scan remote platform probe output line-by-line for supported OS/arch markers.

- Handle PowerShell/OpenSSH first-use CLIXML or banner noise before Windows ARM64.

- Add regression coverage for noisy Windows ARM64 detection.

* fix: harden SSH platform probe markers

---------

Co-authored-by: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com>
This commit is contained in:
Siddiqui Qamar 2026-07-11 02:31:53 -03:00 committed by GitHub
parent 4df9457530
commit e3f0b99f4a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 123 additions and 55 deletions

View File

@ -98,7 +98,7 @@ describe('cross-version isolation', () => {
// $HOME, isRelayAlreadyInstalled probe, lock acquire, upload (no exec),
// npm install, finalize, socket probe, socket poll, then GC scan.
const responses: string[] = [
'Linux x86_64', // uname -sm
'__ORCA_REMOTE_PLATFORM__ Linux x86_64', // tagged POSIX platform probe
'/home/u', // echo $HOME
'MISSING', // isRelayAlreadyInstalled (v2 dir doesn't exist)
'', // mkdir -p remoteRelayDir (v2)

View File

@ -42,7 +42,7 @@ vi.mock('./ssh-relay-deploy-helpers', () => ({
onData: vi.fn(),
onClose: vi.fn()
}),
execCommand: vi.fn().mockResolvedValue('Linux x86_64')
execCommand: vi.fn().mockResolvedValue('__ORCA_REMOTE_PLATFORM__ Linux x86_64')
}))
vi.mock('./ssh-remote-node-resolution', () => ({
@ -123,7 +123,7 @@ describe('deployAndLaunchRelay', () => {
it('calls exec to detect remote platform', async () => {
const conn = makeMockConnection()
const mockExecCommand = vi.mocked(execCommand)
mockExecCommand.mockResolvedValueOnce('Linux x86_64') // uname -sm
mockExecCommand.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Linux x86_64') // tagged POSIX platform probe
mockExecCommand.mockResolvedValueOnce('/home/user') // echo $HOME
mockExecCommand.mockResolvedValueOnce('ORCA-NATIVE-DEPS-OK') // native deps probe
mockExecCommand.mockResolvedValueOnce('DEAD') // socket probe
@ -131,13 +131,16 @@ describe('deployAndLaunchRelay', () => {
await deployAndLaunchRelay(conn)
expect(mockExecCommand).toHaveBeenCalledWith(conn, 'uname -sm')
expect(mockExecCommand).toHaveBeenCalledWith(
conn,
"printf '\\n%s ' '__ORCA_REMOTE_PLATFORM__'; uname -sm"
)
})
it('reports progress via callback', async () => {
const conn = makeMockConnection()
const mockExecCommand = vi.mocked(execCommand)
mockExecCommand.mockResolvedValueOnce('Linux x86_64')
mockExecCommand.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Linux x86_64')
mockExecCommand.mockResolvedValueOnce('/home/user')
mockExecCommand.mockResolvedValueOnce('ORCA-NATIVE-DEPS-OK') // native deps probe
mockExecCommand.mockResolvedValueOnce('DEAD') // socket probe
@ -153,7 +156,7 @@ describe('deployAndLaunchRelay', () => {
it('resolves the remote node path once per deploy', async () => {
const conn = makeMockConnection()
const mockExecCommand = vi.mocked(execCommand)
mockExecCommand.mockResolvedValueOnce('Linux x86_64')
mockExecCommand.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Linux x86_64')
mockExecCommand.mockResolvedValueOnce('/home/user')
mockExecCommand.mockResolvedValueOnce('ORCA-NATIVE-DEPS-OK')
mockExecCommand.mockResolvedValueOnce('DEAD')
@ -167,7 +170,7 @@ describe('deployAndLaunchRelay', () => {
it('resolves node concurrently with remote home, not after the install-state chain', async () => {
const conn = makeMockConnection()
const mockExecCommand = vi.mocked(execCommand)
mockExecCommand.mockResolvedValueOnce('Linux x86_64') // uname -sm
mockExecCommand.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Linux x86_64') // tagged POSIX platform probe
let markNodeResolutionStarted: () => void = () => {}
const nodeResolutionStarted = new Promise<void>((resolve) => {
@ -221,7 +224,7 @@ describe('deployAndLaunchRelay', () => {
const conn = makeMockConnection()
vi.mocked(conn.canRunConcurrentExecCommands).mockReturnValue(false)
const mockExecCommand = vi.mocked(execCommand)
mockExecCommand.mockResolvedValueOnce('Linux x86_64') // uname -sm
mockExecCommand.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Linux x86_64') // tagged POSIX platform probe
let releaseRemoteHome: (home: string) => void = () => {}
let remoteHomeProbeStarted: () => void = () => {}
const remoteHomeProbeStartedPromise = new Promise<void>((resolve) => {
@ -274,7 +277,7 @@ describe('deployAndLaunchRelay', () => {
fallbackInstallStateCompleted = true
return true
})
mockExecCommand.mockResolvedValueOnce('Linux x86_64') // uname -sm
mockExecCommand.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Linux x86_64') // tagged POSIX platform probe
mockExecCommand.mockResolvedValueOnce('/home/user') // concurrent install-state $HOME
mockExecCommand.mockRejectedValueOnce(sessionLimitError) // concurrent node path probe
mockExecCommand.mockResolvedValueOnce('/home/user') // sequential fallback $HOME
@ -302,7 +305,7 @@ describe('deployAndLaunchRelay', () => {
throw sessionLimitError
})
.mockResolvedValueOnce(true)
mockExecCommand.mockResolvedValueOnce('Linux x86_64') // uname -sm
mockExecCommand.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Linux x86_64') // tagged POSIX platform probe
mockExecCommand.mockResolvedValueOnce('/home/user') // concurrent install-state $HOME
mockExecCommand.mockResolvedValueOnce('/home/user') // sequential fallback $HOME
mockExecCommand.mockResolvedValueOnce('ORCA-NATIVE-DEPS-OK') // native deps probe
@ -324,7 +327,7 @@ describe('deployAndLaunchRelay', () => {
const mockExecCommand = vi.mocked(execCommand)
const nodeError = new Error('Node.js not found on remote host')
vi.mocked(resolveRemoteNodePath).mockRejectedValueOnce(nodeError)
mockExecCommand.mockResolvedValueOnce('Linux x86_64') // uname -sm
mockExecCommand.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Linux x86_64') // tagged POSIX platform probe
mockExecCommand.mockResolvedValueOnce('/home/user') // concurrent install-state $HOME
await expect(deployAndLaunchRelay(conn)).rejects.toBe(nodeError)
@ -346,7 +349,7 @@ describe('deployAndLaunchRelay', () => {
})
})
})
mockExecCommand.mockResolvedValueOnce('Linux x86_64') // uname -sm
mockExecCommand.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Linux x86_64') // tagged POSIX platform probe
mockExecCommand.mockResolvedValueOnce('relative-home') // invalid install-state $HOME
const timedDeploy = Promise.race([
@ -370,7 +373,7 @@ describe('deployAndLaunchRelay', () => {
const installError = new Error('permission denied while checking relay install')
vi.mocked(resolveRemoteNodePath).mockRejectedValueOnce(sessionLimitError)
vi.mocked(isRelayAlreadyInstalled).mockRejectedValueOnce(installError)
mockExecCommand.mockResolvedValueOnce('Linux x86_64') // uname -sm
mockExecCommand.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Linux x86_64') // tagged POSIX platform probe
mockExecCommand.mockResolvedValueOnce('/home/user') // concurrent install-state $HOME
await expect(deployAndLaunchRelay(conn)).rejects.toBe(installError)
@ -384,7 +387,7 @@ describe('deployAndLaunchRelay', () => {
const sessionLimitError = Object.assign(new Error('(SSH) Channel open failure: open failed'), {
reason: 4
})
mockExecCommand.mockResolvedValueOnce('Linux x86_64') // uname -sm
mockExecCommand.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Linux x86_64') // tagged POSIX platform probe
let releaseRemoteHome: (home: string) => void = () => {}
let remoteHomeSettled = false
mockExecCommand.mockReturnValueOnce(
@ -420,7 +423,7 @@ describe('deployAndLaunchRelay', () => {
it('defaults fresh relays to keep-alive-until-reset', async () => {
const conn = makeMockConnection()
const mockExecCommand = vi.mocked(execCommand)
mockExecCommand.mockResolvedValueOnce('Linux x86_64')
mockExecCommand.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Linux x86_64')
mockExecCommand.mockResolvedValueOnce('/home/user')
mockExecCommand.mockResolvedValueOnce('ORCA-NATIVE-DEPS-OK')
mockExecCommand.mockResolvedValueOnce('DEAD')
@ -439,7 +442,7 @@ describe('deployAndLaunchRelay', () => {
it('allows an unlimited SSH disconnect grace window', async () => {
const conn = makeMockConnection()
const mockExecCommand = vi.mocked(execCommand)
mockExecCommand.mockResolvedValueOnce('Linux x86_64')
mockExecCommand.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Linux x86_64')
mockExecCommand.mockResolvedValueOnce('/home/user')
mockExecCommand.mockResolvedValueOnce('ORCA-NATIVE-DEPS-OK')
mockExecCommand.mockResolvedValueOnce('DEAD')
@ -458,7 +461,7 @@ describe('deployAndLaunchRelay', () => {
it('clamps configured SSH disconnect grace to the seven-day maximum', async () => {
const conn = makeMockConnection()
const mockExecCommand = vi.mocked(execCommand)
mockExecCommand.mockResolvedValueOnce('Linux x86_64')
mockExecCommand.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Linux x86_64')
mockExecCommand.mockResolvedValueOnce('/home/user')
mockExecCommand.mockResolvedValueOnce('ORCA-NATIVE-DEPS-OK')
mockExecCommand.mockResolvedValueOnce('DEAD')
@ -477,7 +480,7 @@ describe('deployAndLaunchRelay', () => {
it('uses a content-hashed versioned remote install directory', async () => {
const conn = makeMockConnection()
const mockExecCommand = vi.mocked(execCommand)
mockExecCommand.mockResolvedValueOnce('Linux x86_64')
mockExecCommand.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Linux x86_64')
mockExecCommand.mockResolvedValueOnce('/home/user')
mockExecCommand.mockResolvedValueOnce('ORCA-NATIVE-DEPS-OK')
mockExecCommand.mockResolvedValueOnce('DEAD')
@ -522,12 +525,12 @@ describe('deployAndLaunchRelay', () => {
const connB = makeMockConnection()
const mockExecCommand = vi.mocked(execCommand)
mockExecCommand
.mockResolvedValueOnce('Linux x86_64') // uname A
.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Linux x86_64') // tagged POSIX platform probe A
.mockResolvedValueOnce('/home/user') // $HOME A
.mockResolvedValueOnce('ORCA-NATIVE-DEPS-OK') // native deps probe A
.mockResolvedValueOnce('DEAD') // probe A
.mockResolvedValueOnce('READY') // poll A
.mockResolvedValueOnce('Linux x86_64') // uname B
.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Linux x86_64') // tagged POSIX platform probe B
.mockResolvedValueOnce('/home/user') // $HOME B
.mockResolvedValueOnce('ORCA-NATIVE-DEPS-OK') // native deps probe B
.mockResolvedValueOnce('DEAD') // probe B
@ -561,8 +564,8 @@ describe('deployAndLaunchRelay', () => {
const mockExecCommand = vi.mocked(execCommand)
vi.mocked(resolveRemoteNodePath).mockResolvedValue('C:/Program Files/nodejs/node.exe')
mockExecCommand
.mockRejectedValueOnce(new Error('uname not found')) // uname -sm
.mockResolvedValueOnce('Windows X64') // PowerShell platform probe
.mockRejectedValueOnce(new Error('uname not found')) // tagged POSIX platform probe
.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Windows X64') // tagged PowerShell platform probe
.mockResolvedValueOnce('C:\\Users\\me user') // remote home
.mockResolvedValueOnce('ORCA-NATIVE-DEPS-OK') // native deps probe
.mockResolvedValueOnce('') // no persisted active pipe
@ -607,8 +610,8 @@ describe('deployAndLaunchRelay', () => {
onClose: vi.fn()
})
mockExecCommand
.mockRejectedValueOnce(new Error('uname not found')) // uname -sm
.mockResolvedValueOnce('Windows X64') // PowerShell platform probe
.mockRejectedValueOnce(new Error('uname not found')) // tagged POSIX platform probe
.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Windows X64') // tagged PowerShell platform probe
.mockResolvedValueOnce('C:\\Users\\me user') // remote home
.mockResolvedValueOnce('ORCA-NATIVE-DEPS-OK') // native deps probe
.mockResolvedValueOnce('') // no persisted active pipe yet
@ -654,8 +657,8 @@ describe('deployAndLaunchRelay', () => {
const persistedPipe = '\\\\.\\pipe\\orca-relay-1234567890abcdef1234'
vi.mocked(resolveRemoteNodePath).mockResolvedValue('C:/Program Files/nodejs/node.exe')
mockExecCommand
.mockRejectedValueOnce(new Error('uname not found')) // uname -sm
.mockResolvedValueOnce('Windows X64') // PowerShell platform probe
.mockRejectedValueOnce(new Error('uname not found')) // tagged POSIX platform probe
.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Windows X64') // tagged PowerShell platform probe
.mockResolvedValueOnce('C:\\Users\\me user') // remote home
.mockResolvedValueOnce('ORCA-NATIVE-DEPS-OK') // native deps probe
.mockResolvedValueOnce(`${persistedPipe}\n`) // persisted active pipe marker
@ -682,8 +685,8 @@ describe('deployAndLaunchRelay', () => {
const mockExecCommand = vi.mocked(execCommand)
vi.mocked(resolveRemoteNodePath).mockResolvedValue('C:/Program Files/nodejs/node.exe')
mockExecCommand
.mockRejectedValueOnce(new Error('uname not found')) // uname A
.mockResolvedValueOnce('Windows X64')
.mockRejectedValueOnce(new Error('uname not found')) // tagged POSIX platform probe A
.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Windows X64')
.mockResolvedValueOnce('C:\\Users\\me user')
.mockResolvedValueOnce('ORCA-NATIVE-DEPS-OK')
.mockResolvedValueOnce('') // no persisted active pipe A
@ -691,8 +694,8 @@ describe('deployAndLaunchRelay', () => {
.mockResolvedValueOnce('')
.mockResolvedValueOnce('READY')
.mockResolvedValueOnce('') // persist active pipe A
.mockRejectedValueOnce(new Error('uname not found')) // uname B
.mockResolvedValueOnce('Windows X64')
.mockRejectedValueOnce(new Error('uname not found')) // tagged POSIX platform probe B
.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Windows X64')
.mockResolvedValueOnce('C:\\Users\\me user')
.mockResolvedValueOnce('ORCA-NATIVE-DEPS-OK')
.mockResolvedValueOnce('') // no persisted active pipe B

View File

@ -152,7 +152,7 @@ function makeExecResponses(opts: {
// build toolchain — no chmod/probe/launch slots are reached.
if (opts.npmInstall !== 'ok') {
return [
'Linux x86_64',
'__ORCA_REMOTE_PLATFORM__ Linux x86_64',
'/home/u',
'', // mkdir remoteDir (uploadRelay)
'', // chmod +x node
@ -171,7 +171,7 @@ function makeExecResponses(opts: {
? { reject: 'cd: no such file or directory' }
: opts.probe
const slots: ExecResponse[] = [
'Linux x86_64',
'__ORCA_REMOTE_PLATFORM__ Linux x86_64',
'/home/u',
'', // mkdir remoteDir (uploadRelay)
'', // chmod +x node
@ -530,7 +530,7 @@ describe('installNativeDeps (via deployAndLaunchRelay)', () => {
vi.mocked(resolveRemoteNodePath).mockResolvedValueOnce('C:/Program Files/nodejs/node.exe')
const conn = makeMockConnection(sftpCapture)
feed([
'Windows AMD64',
'__ORCA_REMOTE_PLATFORM__ Windows AMD64',
'C:\\Users\\u',
'', // mkdir remoteDir
'', // npm install native deps
@ -605,7 +605,7 @@ describe('installNativeDeps (via deployAndLaunchRelay)', () => {
vi.mocked(isRelayAlreadyInstalled).mockResolvedValue(true)
const conn = makeMockConnection(sftpCapture)
feed([
'Linux x86_64',
'__ORCA_REMOTE_PLATFORM__ Linux x86_64',
'/home/u',
'MISSING', // first native-deps probe before lock
'MISSING', // re-probe after lock
@ -632,7 +632,13 @@ describe('installNativeDeps (via deployAndLaunchRelay)', () => {
it('does not mutate an existing relay dir when required native deps are present', async () => {
vi.mocked(isRelayAlreadyInstalled).mockResolvedValue(true)
const conn = makeMockConnection(sftpCapture)
feed(['Linux x86_64', '/home/u', 'ORCA-NATIVE-DEPS-OK', 'DEAD', 'READY'])
feed([
'__ORCA_REMOTE_PLATFORM__ Linux x86_64',
'/home/u',
'ORCA-NATIVE-DEPS-OK',
'DEAD',
'READY'
])
await deployAndLaunchRelay(conn)

View File

@ -11,13 +11,18 @@ const { detectRemoteHostPlatform } = await import('./ssh-remote-platform-detecti
const conn = {} as SshConnection
function decodePowerShellCommand(command: string): string {
const match = command.match(/-EncodedCommand\s+([A-Za-z0-9+/=]+)/)
return match ? Buffer.from(match[1], 'base64').toString('utf16le') : ''
}
describe('detectRemoteHostPlatform', () => {
beforeEach(() => {
execCommandMock.mockReset()
})
it('detects POSIX hosts from uname output', async () => {
execCommandMock.mockResolvedValueOnce('Linux x86_64\n')
execCommandMock.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Linux x86_64\n')
await expect(detectRemoteHostPlatform(conn)).resolves.toMatchObject({
relayPlatform: 'linux-x64',
@ -25,13 +30,16 @@ describe('detectRemoteHostPlatform', () => {
arch: 'x64',
pathFlavor: 'posix'
})
expect(execCommandMock).toHaveBeenCalledWith(conn, 'uname -sm')
expect(execCommandMock).toHaveBeenCalledWith(
conn,
"printf '\\n%s ' '__ORCA_REMOTE_PLATFORM__'; uname -sm"
)
})
it('falls back to PowerShell detection for Windows remotes', async () => {
execCommandMock
.mockRejectedValueOnce(new Error('uname unavailable'))
.mockResolvedValueOnce('Windows AMD64\r\n')
.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Windows AMD64\r\n')
await expect(detectRemoteHostPlatform(conn)).resolves.toMatchObject({
relayPlatform: 'win32-x64',
@ -45,17 +53,50 @@ describe('detectRemoteHostPlatform', () => {
expect.stringContaining('powershell.exe'),
{ wrapCommand: false }
)
const command = execCommandMock.mock.calls[1]?.[1] ?? ''
expect(decodePowerShellCommand(command)).toContain(
'Write-Output ("`n__ORCA_REMOTE_PLATFORM__ Windows " + $arch)'
)
})
it('ignores untagged platforms before the tagged Windows ARM64 result', async () => {
execCommandMock
.mockRejectedValueOnce(new Error('uname unavailable'))
.mockResolvedValueOnce(
'Linux x86_64\r\nWindows AMD64\r\n#< CLIXML\r\n' +
'__ORCA_REMOTE_PLATFORM__ Windows ARM64\r\n'
)
await expect(detectRemoteHostPlatform(conn)).resolves.toMatchObject({
relayPlatform: 'win32-arm64',
os: 'win32',
arch: 'arm64',
pathFlavor: 'windows'
})
})
it('ignores a marker concatenated to unterminated startup noise', async () => {
execCommandMock.mockResolvedValueOnce(
'startup noise__ORCA_REMOTE_PLATFORM__ Linux x86_64\n' +
'__ORCA_REMOTE_PLATFORM__ Linux arm64\n'
)
await expect(detectRemoteHostPlatform(conn)).resolves.toMatchObject({
relayPlatform: 'linux-arm64'
})
})
it('returns null when neither probe yields a supported platform', async () => {
execCommandMock.mockResolvedValueOnce('Linux').mockResolvedValueOnce('FreeBSD x86_64')
execCommandMock
.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Linux')
.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ FreeBSD x86_64')
await expect(detectRemoteHostPlatform(conn)).resolves.toBeNull()
})
it('does not use whitespace regex splitting for remote platform output', async () => {
const splitSpy = vi.spyOn(String.prototype, 'split')
execCommandMock.mockResolvedValueOnce('Darwin arm64 extra')
execCommandMock.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Darwin arm64 extra')
await expect(detectRemoteHostPlatform(conn)).resolves.toMatchObject({
relayPlatform: 'darwin-arm64'

View File

@ -1,10 +1,15 @@
import type { SshConnection } from './ssh-connection'
import { getProcessOutputFields } from '../../shared/process-output-field-scanner'
import {
getProcessOutputFields,
iterateProcessOutputLines
} from '../../shared/process-output-field-scanner'
import { parseUnameToRelayPlatform, type RelayPlatform } from './relay-protocol'
import { execCommand } from './ssh-relay-deploy-helpers'
import { getRemoteHostPlatform, type RemoteHostPlatform } from './ssh-remote-platform'
import { powerShellCommand } from './ssh-remote-powershell'
const PLATFORM_PROBE_MARKER = '__ORCA_REMOTE_PLATFORM__'
export async function detectRemoteHostPlatform(
conn: SshConnection
): Promise<RemoteHostPlatform | null> {
@ -18,12 +23,12 @@ export async function detectRemoteHostPlatform(
async function detectUnamePlatform(conn: SshConnection): Promise<RelayPlatform | null> {
try {
const output = await execCommand(conn, 'uname -sm')
const parts = getProcessOutputFields(output, 2)
if (parts.length < 2) {
return null
}
return parseUnameToRelayPlatform(parts[0], parts[1])
const output = await execCommand(
conn,
// Why: Remote startup output may omit its trailing newline and must not absorb the marker.
`printf '\\n%s ' '${PLATFORM_PROBE_MARKER}'; uname -sm`
)
return parseRemotePlatformOutput(output)
} catch {
return null
}
@ -35,15 +40,27 @@ async function detectWindowsPlatform(conn: SshConnection): Promise<RelayPlatform
'$arch = $env:PROCESSOR_ARCHITECTURE',
'try { $runtimeArch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString(); if ($runtimeArch) { $arch = $runtimeArch } } catch {}',
'if (-not $arch) { $arch = $env:PROCESSOR_ARCHITECTURE }',
'Write-Output ("Windows " + $arch)'
// Why: Remote startup output may omit its trailing newline and must not absorb the marker.
`Write-Output ("\`n${PLATFORM_PROBE_MARKER} Windows " + $arch)`
].join('; ')
const output = await execCommand(conn, powerShellCommand(script), { wrapCommand: false })
const parts = getProcessOutputFields(output, 2)
if (parts.length < 2 || parts[0].toLowerCase() !== 'windows') {
return null
}
return parseUnameToRelayPlatform('Windows', parts[1])
return parseRemotePlatformOutput(output)
} catch {
return null
}
}
function parseRemotePlatformOutput(output: string): RelayPlatform | null {
// Why: SSH startup noise can resemble valid probe output and select the wrong relay.
for (const line of iterateProcessOutputLines(output)) {
const parts = getProcessOutputFields(line, 3)
if (parts.length < 3 || parts[0] !== PLATFORM_PROBE_MARKER) {
continue
}
const platform = parseUnameToRelayPlatform(parts[1], parts[2])
if (platform) {
return platform
}
}
return null
}

View File

@ -35,7 +35,7 @@ describe('joinRemotePath', () => {
describe('detectRemoteHostPlatform', () => {
it('uses uname when the remote is POSIX', async () => {
vi.mocked(execCommand).mockResolvedValueOnce('Darwin arm64')
vi.mocked(execCommand).mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Darwin arm64')
await expect(detectRemoteHostPlatform(conn)).resolves.toMatchObject({
relayPlatform: 'darwin-arm64',
@ -46,7 +46,7 @@ describe('detectRemoteHostPlatform', () => {
it('falls back to PowerShell when uname is unavailable on Windows', async () => {
vi.mocked(execCommand)
.mockRejectedValueOnce(new Error('uname not recognized'))
.mockResolvedValueOnce('Windows AMD64')
.mockResolvedValueOnce('__ORCA_REMOTE_PLATFORM__ Windows AMD64')
await expect(detectRemoteHostPlatform(conn)).resolves.toMatchObject({
relayPlatform: 'win32-x64',
@ -59,5 +59,6 @@ describe('detectRemoteHostPlatform', () => {
expect(script).toContain('$arch = $env:PROCESSOR_ARCHITECTURE')
expect(script).toContain('try { $runtimeArch =')
expect(script).toContain('catch {}')
expect(script).toContain('Write-Output ("`n__ORCA_REMOTE_PLATFORM__ Windows " + $arch)')
})
})