diff --git a/src/main/native-chat/wsl-transcript-fs-process-spawn.test.ts b/src/main/native-chat/wsl-transcript-fs-process-spawn.test.ts new file mode 100644 index 00000000000..8547ad2e601 --- /dev/null +++ b/src/main/native-chat/wsl-transcript-fs-process-spawn.test.ts @@ -0,0 +1,46 @@ +import { afterEach, describe, expect, it, vi } from 'vitest' +import { forkWslTranscriptFsProcess } from './wsl-transcript-fs-process-spawn' + +const fork = vi.hoisted(() => vi.fn()) +vi.mock('node:child_process', () => ({ fork })) +vi.mock('node:fs', () => ({ existsSync: () => true })) + +afterEach(() => { + vi.unstubAllGlobals() + vi.clearAllMocks() +}) + +describe('WSL transcript reader runtime isolation', () => { + it.each(['darwin', 'linux', 'win32'])('isolates owned Bun reads on %s', (platform) => { + vi.stubGlobal('process', { + ...process, + platform, + versions: { ...process.versions, bun: '1.4.2' } + }) + forkWslTranscriptFsProcess() + expect(fork).toHaveBeenCalledWith( + expect.any(String), + [], + expect.objectContaining({ + execArgv: [ + '--no-env-file', + platform === 'win32' ? '--config=NUL' : '--config=/dev/null', + '--no-install' + ] + }) + ) + }) + + it('preserves the Node and Electron launch policy', () => { + vi.stubGlobal('process', { ...process, versions: { ...process.versions, bun: undefined } }) + forkWslTranscriptFsProcess() + expect(fork).toHaveBeenCalledWith( + expect.any(String), + [], + expect.objectContaining({ + execArgv: [], + env: expect.objectContaining({ ELECTRON_RUN_AS_NODE: '1' }) + }) + ) + }) +}) diff --git a/src/main/native-chat/wsl-transcript-fs-process-spawn.ts b/src/main/native-chat/wsl-transcript-fs-process-spawn.ts index 5d67cba8bed..7b7fcd8b0cb 100644 --- a/src/main/native-chat/wsl-transcript-fs-process-spawn.ts +++ b/src/main/native-chat/wsl-transcript-fs-process-spawn.ts @@ -1,6 +1,7 @@ import { fork, type ChildProcess } from 'node:child_process' import { existsSync } from 'node:fs' import { join } from 'node:path' +import { bunOwnedRuntimeArgs } from '../../shared/bun-owned-runtime-args' import { pickAllowedEnv, RUNTIME_ENV_ALLOWLIST } from '../ai-vault/session-scanner-service-env' const PROCESS_ENTRY_FILENAME = 'wsl-transcript-fs-process-entry.js' @@ -50,7 +51,7 @@ export function forkWslTranscriptFsProcess(): ChildProcess { } return fork(entryPath, [], { env: wslTranscriptFsProcessForkEnv(), - execArgv: [], + execArgv: process.versions.bun ? bunOwnedRuntimeArgs() : [], serialization: 'advanced', stdio: ['ignore', 'ignore', 'ignore', 'ipc'], ...(process.platform === 'win32' ? { windowsHide: true } : {})