From b7ad8be42ee6dd26936474ee1fb1ca52209533bf Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Mon, 26 Oct 2026 13:17:06 -0700 Subject: [PATCH] refactor(agent-hooks): launch the WSL hook relay through the process host (#34051) Build the wsl.exe argv with buildWslExecArgs and start it with spawnProcess from @orca/process-host instead of a hand-built node:child_process spawn. --- .../agent-hooks/wsl-hook-relay-launch.test.ts | 27 +++++++++++-------- src/main/agent-hooks/wsl-hook-relay-launch.ts | 12 ++++++--- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/main/agent-hooks/wsl-hook-relay-launch.test.ts b/src/main/agent-hooks/wsl-hook-relay-launch.test.ts index 4a0fb53dbb3..17e33d01c9e 100644 --- a/src/main/agent-hooks/wsl-hook-relay-launch.test.ts +++ b/src/main/agent-hooks/wsl-hook-relay-launch.test.ts @@ -1,18 +1,21 @@ import { describe, expect, it, vi } from 'vitest' import { execFileSync } from 'node:child_process' -import type * as ChildProcess from 'node:child_process' +import type * as ProcessHost from '@orca/process-host' import { chmodSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' import { resetWslInteropSpawnDirectoryCache } from '../wsl-interop-spawn-directory' -const { spawnMock } = vi.hoisted(() => ({ - spawnMock: vi.fn((..._args: unknown[]) => ({ pid: 1 })) +const { spawnProcessMock } = vi.hoisted(() => ({ + spawnProcessMock: vi.fn((..._args: unknown[]) => ({ pid: 1 })) })) -vi.mock('node:child_process', async (importOriginal) => ({ - ...(await importOriginal()), - spawn: spawnMock +vi.mock('@orca/process-host', async (importOriginal) => ({ + ...(await importOriginal()), + spawnProcess: spawnProcessMock +})) +vi.mock('../wsl/wsl-executable-path', () => ({ + resolveWslExecutablePath: () => 'C:\\Windows\\System32\\wsl.exe' })) import { @@ -31,9 +34,9 @@ describe('spawnWslRelayProcess', () => { try { spawnWslRelayProcess('Ubuntu', env, '1.2.3') - expect(spawnMock).toHaveBeenCalledExactlyOnceWith( - 'wsl.exe', - [ + expect(spawnProcessMock).toHaveBeenCalledExactlyOnceWith({ + program: 'C:\\Windows\\System32\\wsl.exe', + args: [ '-d', 'Ubuntu', '--exec', @@ -41,8 +44,10 @@ describe('spawnWslRelayProcess', () => { '-c', 'exec sh "$HOME/.orca-wsl/hook-relay/1.2.3/launch.sh"' ], - { env, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true, cwd: systemRoot } - ) + env, + stdio: 'pipe', + cwd: systemRoot + }) } finally { vi.unstubAllEnvs() resetWslInteropSpawnDirectoryCache() diff --git a/src/main/agent-hooks/wsl-hook-relay-launch.ts b/src/main/agent-hooks/wsl-hook-relay-launch.ts index 36155fba1a3..364aa5cfab8 100644 --- a/src/main/agent-hooks/wsl-hook-relay-launch.ts +++ b/src/main/agent-hooks/wsl-hook-relay-launch.ts @@ -1,6 +1,7 @@ -import { spawn, type ChildProcessWithoutNullStreams } from 'node:child_process' +import type { ChildProcessWithoutNullStreams } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { join } from 'node:path' +import { spawnProcess } from '@orca/process-host' import { relayBundleCandidates } from '../ssh/relay-bundle-paths' import { withWslGuestLauncherRevision } from '../wsl/wsl-guest-launcher-revision' import { orcadPosixNodeRuntimeGlobs } from '../managed-server/install/orcad-node-runtime-globs' @@ -13,6 +14,8 @@ import { } from './wsl-hook-relay-sentinel' import { addOrcaWslInteropEnv } from '../pty/wsl-orca-env' import { runWslProcess } from '../wsl/wsl-runner' +import { resolveWslExecutablePath } from '../wsl/wsl-executable-path' +import { buildWslExecArgs } from '../../shared/wsl-login-shell-command' import { resolveWslInteropSpawnCwd } from '../wsl-interop-spawn-directory' import { WSL_HOOK_RELAY_BUNDLE_NAME, @@ -102,10 +105,11 @@ export function spawnWslRelayProcess( ): ChildProcessWithoutNullStreams { // --exec bypasses login-shell rewriting and passes $HOME verbatim for guest-side expansion. const command = `exec sh "${guestRelayDirExpr(version)}/launch.sh"` - return spawn('wsl.exe', ['-d', distro, '--exec', 'sh', '-c', command], { + return spawnProcess({ + program: resolveWslExecutablePath(), + args: buildWslExecArgs(distro, ['sh', '-c', command]), env, - stdio: ['pipe', 'pipe', 'pipe'], - windowsHide: true, + stdio: 'pipe', // Use a stable Windows cwd so deleting the current worktree cannot break later relay launches. cwd: resolveWslInteropSpawnCwd() })