Refresh Windows PATH for local preflight probes (#5442)

Co-authored-by: Neil <neil@stably.ai>
This commit is contained in:
OrcaWin 2026-06-15 18:35:28 -07:00 committed by GitHub
parent 1d05e72de3
commit a32fe5d823
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 53 additions and 3 deletions

View File

@ -0,0 +1,12 @@
import { mergePersistedWindowsPath } from '../pty/windows-environment-path'
export function buildLocalPreflightEnv(): NodeJS.ProcessEnv | undefined {
if (process.platform !== 'win32') {
return undefined
}
const env = { ...process.env }
// Why: newly installed CLIs update persisted Windows Path, but the running
// Electron process keeps its old environment until we merge it explicitly.
mergePersistedWindowsPath(env)
return env
}

View File

@ -12,7 +12,8 @@ const {
getBitbucketAuthStatusMock,
getAzureDevOpsAuthStatusMock,
getGiteaAuthStatusMock,
resolveCliCommandsMock
resolveCliCommandsMock,
mergePersistedWindowsPathMock
} = vi.hoisted(() => ({
handleMock: vi.fn(),
execFileMock: vi.fn(),
@ -23,7 +24,8 @@ const {
getBitbucketAuthStatusMock: vi.fn(),
getAzureDevOpsAuthStatusMock: vi.fn(),
getGiteaAuthStatusMock: vi.fn(),
resolveCliCommandsMock: vi.fn()
resolveCliCommandsMock: vi.fn(),
mergePersistedWindowsPathMock: vi.fn()
}))
vi.mock('electron', () => ({
@ -51,6 +53,10 @@ vi.mock('../codex-cli/command', () => ({
resolveCliCommands: resolveCliCommandsMock
}))
vi.mock('../pty/windows-environment-path', () => ({
mergePersistedWindowsPath: mergePersistedWindowsPathMock
}))
vi.mock('./ssh', () => ({
getActiveMultiplexer: getActiveMultiplexerMock
}))
@ -104,6 +110,7 @@ describe('preflight', () => {
getBitbucketAuthStatusMock.mockReset()
getAzureDevOpsAuthStatusMock.mockReset()
getGiteaAuthStatusMock.mockReset()
mergePersistedWindowsPathMock.mockReset()
// Why: existing tests should keep treating `which` as the only source
// unless a case explicitly exercises the install-dir fallback.
resolveCliCommandsMock.mockReset()
@ -304,6 +311,34 @@ describe('preflight', () => {
)
})
it('uses the persisted Windows Path when probing host CLIs', async () => {
Object.defineProperty(process, 'platform', {
configurable: true,
value: 'win32'
})
mergePersistedWindowsPathMock.mockImplementation((env: Record<string, string>) => {
env.Path = 'C:\\Windows\\System32;C:\\Program Files\\GitHub CLI'
})
execFileAsyncMock
.mockResolvedValueOnce({ stdout: 'git version 2.0.0\n' })
.mockResolvedValueOnce({ stdout: 'gh version 2.0.0\n' })
.mockResolvedValueOnce({ stdout: 'glab version 1.92.1\n' })
.mockResolvedValueOnce({ stdout: 'github.com\n - Active account: true\n' })
.mockResolvedValueOnce({ stdout: 'Logged in to gitlab.com\n' })
const status = await runPreflightCheck()
expect(status.gh).toEqual({ installed: true, authenticated: true })
expect(mergePersistedWindowsPathMock).toHaveBeenCalled()
expect(execFileAsyncMock).toHaveBeenNthCalledWith(2, 'gh', ['--version'], {
encoding: 'utf-8',
timeout: 5000,
env: expect.objectContaining({
Path: 'C:\\Windows\\System32;C:\\Program Files\\GitHub CLI'
})
})
})
it('times out hung WSL preflight probes', async () => {
vi.useFakeTimers()
try {

View File

@ -13,6 +13,7 @@ import { getActiveMultiplexer } from './ssh'
import { detectWslCommandsOnPath, type WslPreflightTarget } from './preflight-wsl-agent-detection'
import { runPreflightCommandInWsl } from './preflight-wsl-command'
import { detectCommandsInInstallDirs } from './local-agent-install-dir-detection'
import { buildLocalPreflightEnv } from './preflight-local-env'
const execFileAsync = promisify(execFile)
const PREFLIGHT_COMMAND_TIMEOUT_MS = 5000
@ -91,9 +92,11 @@ async function execLocalPreflightCommand(
command: string,
args: string[]
): Promise<PreflightCommandResult> {
const env = buildLocalPreflightEnv()
const commandPromise = execFileAsync(command, args, {
encoding: 'utf-8',
timeout: PREFLIGHT_COMMAND_TIMEOUT_MS
timeout: PREFLIGHT_COMMAND_TIMEOUT_MS,
...(env ? { env } : {})
}) as Promise<PreflightCommandResult>
return withPreflightTimeout(command, commandPromise)