mirror of
https://github.com/stablyai/orca.git
synced 2026-10-16 16:03:26 +00:00
fix: address pr-bug-scan validated finding from #6952
throwNodeNotFound() now re-raises AbortError when the shared signal is aborted, so a signal-cancelled node probe no longer launders into 'Node.js not found'; sequential fallback runs.
This commit is contained in:
@@ -331,6 +331,25 @@ describe('resolveRemoteNodePath', () => {
|
||||
).rejects.toBe(sessionLimitError)
|
||||
})
|
||||
|
||||
it('surfaces an AbortError instead of not-found when the shared signal is aborted', async () => {
|
||||
// Why: in concurrent bootstrap a sibling probe aborts this resolution via
|
||||
// the shared signal. Each strategy catch swallows the injected AbortError,
|
||||
// so without re-raising it here the resolver would launder cancellation
|
||||
// into a fatal "Node.js not found" and defeat the sequential fallback.
|
||||
const controller = new AbortController()
|
||||
const abortError = Object.assign(new Error('SSH operation was cancelled'), {
|
||||
name: 'AbortError'
|
||||
})
|
||||
execCommandMock.mockImplementation(() => {
|
||||
controller.abort()
|
||||
return Promise.reject(abortError)
|
||||
})
|
||||
|
||||
await expect(
|
||||
resolveRemoteNodePath(conn, undefined, { signal: controller.signal })
|
||||
).rejects.toMatchObject({ name: 'AbortError' })
|
||||
})
|
||||
|
||||
it('throws when every candidate across both strategies is below the minimum', async () => {
|
||||
execCommandMock
|
||||
.mockResolvedValueOnce('/old/node\n') // path probe
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { SshConnection } from './ssh-connection'
|
||||
import { shellEscape } from './ssh-connection-utils'
|
||||
import { createSshOperationAbortError, shellEscape } from './ssh-connection-utils'
|
||||
import type { RemoteHostPlatform } from './ssh-remote-platform'
|
||||
import { isWindowsRemoteHost, normalizeWindowsRemotePath } from './ssh-remote-platform'
|
||||
import { powerShellCommand } from './ssh-remote-powershell'
|
||||
@@ -47,7 +47,7 @@ export async function resolveRemoteNodePath(
|
||||
return loginShellPath
|
||||
}
|
||||
|
||||
throwNodeNotFound()
|
||||
throwNodeNotFound(options)
|
||||
}
|
||||
|
||||
// Probe the on-disk install directories of every common Node version manager
|
||||
@@ -245,10 +245,18 @@ async function resolveRemoteWindowsNodePath(
|
||||
// Fall through to the shared error below.
|
||||
}
|
||||
|
||||
throwNodeNotFound()
|
||||
throwNodeNotFound(options)
|
||||
}
|
||||
|
||||
function throwNodeNotFound(): never {
|
||||
function throwNodeNotFound(options?: RemoteNodeResolutionOptions): never {
|
||||
// Why: when a sibling bootstrap probe aborts this resolution via the shared
|
||||
// signal, the strategy catch-blocks swallow the injected AbortError and fall
|
||||
// through to here. Surfacing "Node.js not found" would launder a cancellation
|
||||
// into a fatal non-session failure, defeating the concurrent-bootstrap
|
||||
// sequential fallback. Re-raise the abort so callers classify it correctly.
|
||||
if (options?.signal?.aborted) {
|
||||
throw createSshOperationAbortError()
|
||||
}
|
||||
throw new Error(
|
||||
'Node.js not found on remote host. Orca relay requires Node.js 18+. ' +
|
||||
'Install Node.js on the remote and try again.'
|
||||
|
||||
Reference in New Issue
Block a user