From 5c96e1bcbf92397bc8cdd7ea8e67f862218cc8d2 Mon Sep 17 00:00:00 2001 From: Merge Sim Date: Mon, 31 Aug 2026 20:43:56 -0700 Subject: [PATCH] fix(claude): route stream-json launch through process wrapper --- .../claude/claude-stream-json-connection.test.ts | 16 +++++++--------- src/main/claude/claude-stream-json-connection.ts | 13 +++++++------ 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/main/claude/claude-stream-json-connection.test.ts b/src/main/claude/claude-stream-json-connection.test.ts index b6de3a24680..eab2eb332a0 100644 --- a/src/main/claude/claude-stream-json-connection.test.ts +++ b/src/main/claude/claude-stream-json-connection.test.ts @@ -1,7 +1,7 @@ import { EventEmitter } from 'node:events' import { PassThrough } from 'node:stream' import { afterEach, describe, expect, it, vi } from 'vitest' -import type { spawn } from 'node:child_process' +import type { spawnProcess } from '../../shared/child-process/run-process' import { openClaudeStreamJsonConnection, type ClaudeControlRequest @@ -22,10 +22,8 @@ function fakeSpawn() { child.stdout = new PassThrough() child.stderr = new PassThrough() child.kill = vi.fn(() => true) - const spawnMock = vi.fn( - (_command: string, _args: readonly string[], _options: { env?: NodeJS.ProcessEnv }) => child - ) - const spawnImpl = spawnMock as unknown as typeof spawn + const spawnMock = vi.fn((_spec: Parameters[0]) => child) + const spawnImpl = spawnMock as unknown as typeof spawnProcess return { child, spawnImpl, spawnMock } } @@ -75,12 +73,12 @@ describe('Claude stream-json connection', () => { await expect(listing).resolves.toEqual({ models: [{ value: 'sonnet' }] }) expect(process.spawnImpl).toHaveBeenCalledWith( - 'claude', - ['-p'], expect.objectContaining({ + program: 'claude', + args: ['-p'], cwd: '/work/repo', env: expect.objectContaining({ CLAUDE_CONFIG_DIR: '/accounts/one' }), - windowsHide: true + stdio: ['pipe', 'pipe', 'pipe'] }) ) }) @@ -173,7 +171,7 @@ describe('Claude stream-json connection', () => { process.spawnImpl ) - const env = process.spawnMock.mock.calls[0]?.[2]?.env + const env = process.spawnMock.mock.calls[0]?.[0]?.env expect(env).toMatchObject({ ANTHROPIC_AUTH_TOKEN: 'configured-token', ANTHROPIC_BASE_URL: 'https://gateway.example.test', diff --git a/src/main/claude/claude-stream-json-connection.ts b/src/main/claude/claude-stream-json-connection.ts index 0d5d7e01488..80b35cedadb 100644 --- a/src/main/claude/claude-stream-json-connection.ts +++ b/src/main/claude/claude-stream-json-connection.ts @@ -1,4 +1,4 @@ -import { spawn, type ChildProcessWithoutNullStreams } from 'node:child_process' +import { spawnProcess } from '../../shared/child-process/run-process' import { waitForProcessExitUntil } from '../codex/codex-process-exit-deadline' import { killCodexAppServerProcessTree } from '../codex/codex-app-server-session' import { buildClaudeChildProcessEnv } from './claude-child-process-environment' @@ -82,14 +82,15 @@ function exitError(stderrTail: string, cause?: Error): Error { export async function openClaudeStreamJsonConnection( launch: ClaudeStreamJsonLaunch, handlers: ClaudeStreamJsonConnectionHandlers = {}, - spawnImpl: typeof spawn = spawn + spawnImpl: typeof spawnProcess = spawnProcess ): Promise { - const child = spawnImpl(launch.command, launch.args, { + const child = spawnImpl({ + program: launch.command, + args: launch.args, cwd: launch.cwd, env: buildClaudeChildProcessEnv(launch.env), - stdio: ['pipe', 'pipe', 'pipe'], - windowsHide: true - }) as ChildProcessWithoutNullStreams + stdio: ['pipe', 'pipe', 'pipe'] + }) const pending = new Map() let nextRequestId = 1 let stderrTail = ''