From 0bde66b0321747ed6e167373ea6ca08984de1fa9 Mon Sep 17 00:00:00 2001 From: 777genius Date: Thu, 23 Apr 2026 21:29:19 +0300 Subject: [PATCH] fix(runtime): pass provider args through codex probes --- .../CliProviderModelAvailabilityService.ts | 23 +++-- .../services/team/TeamProvisioningService.ts | 46 ++++++--- ...liProviderModelAvailabilityService.test.ts | 25 +++++ .../TeamProvisioningServicePrepare.test.ts | 94 +++++++++++++++++++ 4 files changed, 165 insertions(+), 23 deletions(-) diff --git a/src/main/services/runtime/CliProviderModelAvailabilityService.ts b/src/main/services/runtime/CliProviderModelAvailabilityService.ts index 0637839a..62e7f46d 100644 --- a/src/main/services/runtime/CliProviderModelAvailabilityService.ts +++ b/src/main/services/runtime/CliProviderModelAvailabilityService.ts @@ -44,7 +44,7 @@ interface ProviderModelAvailabilityCacheEntry { providerId: CliProviderId; signature: string; snapshot: ProviderModelAvailabilitySnapshot; - envPromise: Promise; + cliEnvPromise: Promise<{ env: NodeJS.ProcessEnv; providerArgs: string[] }>; } type ProviderAvailabilityUpdateHandler = ( @@ -190,10 +190,13 @@ export class CliProviderModelAvailabilityService { providerId: context.provider.providerId, signature, snapshot: createCheckingSnapshot(signature, visibleModels), - envPromise: buildProviderAwareCliEnv({ + cliEnvPromise: buildProviderAwareCliEnv({ binaryPath: context.binaryPath, providerId: context.provider.providerId, - }).then((result) => result.env), + }).then((result) => ({ + env: result.env, + providerArgs: result.providerArgs ?? [], + })), }; this.cache.set(signature, entry); this.startProbes(context, entry); @@ -268,11 +271,15 @@ export class CliProviderModelAvailabilityService { modelId: string ): Promise> { try { - const env = await entry.envPromise; - const { stdout } = await execCli(context.binaryPath, buildProviderModelProbeArgs(modelId), { - timeout: getProviderModelProbeTimeoutMs(context.provider.providerId), - env, - }); + const { env, providerArgs } = await entry.cliEnvPromise; + const { stdout } = await execCli( + context.binaryPath, + [...buildProviderModelProbeArgs(modelId), ...providerArgs], + { + timeout: getProviderModelProbeTimeoutMs(context.provider.providerId), + env, + } + ); const output = stdout.trim(); if (isProviderModelProbeSuccessOutput(output)) { return { diff --git a/src/main/services/team/TeamProvisioningService.ts b/src/main/services/team/TeamProvisioningService.ts index ae74eb9b..cd398961 100644 --- a/src/main/services/team/TeamProvisioningService.ts +++ b/src/main/services/team/TeamProvisioningService.ts @@ -3563,11 +3563,13 @@ export class TeamProvisioningService { cwd: string; providerId: TeamProviderId; env: NodeJS.ProcessEnv; + providerArgs?: string[]; limitContext?: boolean; }): Promise { + const providerArgs = params.providerArgs ?? []; const modelListPromise = execCli( params.claudePath, - ['model', 'list', '--json', '--provider', params.providerId], + ['model', 'list', '--json', '--provider', params.providerId, ...providerArgs], { cwd: params.cwd, env: params.env, @@ -3578,7 +3580,7 @@ export class TeamProvisioningService { params.providerId === 'codex' || params.providerId === 'anthropic' ? execCli( params.claudePath, - ['runtime', 'status', '--json', '--provider', params.providerId], + ['runtime', 'status', '--json', '--provider', params.providerId, ...providerArgs], { cwd: params.cwd, env: params.env, @@ -7434,11 +7436,11 @@ export class TeamProvisioningService { const isAuthFailure = this.isAuthFailureWarning(probeResult.warning, 'probe'); const isBlockingPreflightWarning = authSource === 'configured_api_key_missing' || - (((authSource === 'none' || + ((authSource === 'none' || authSource === 'codex_runtime' || authSource === 'gemini_runtime') && isAuthFailure) || - isBinaryProbeWarning(probeResult.warning)); + isBinaryProbeWarning(probeResult.warning); if (authSource === 'configured_api_key_missing') { blockingMessages.push(prefixedWarning); } else if ( @@ -7849,12 +7851,13 @@ export class TeamProvisioningService { return { details, warnings, blockingMessages }; } - const { env } = await this.buildProvisioningEnv(providerId); + const { env, providerArgs = [] } = await this.buildProvisioningEnv(providerId); const runtimeFacts = await this.readRuntimeProviderLaunchFacts({ claudePath, cwd, providerId, env, + providerArgs, limitContext, }); const probeOutcomeByResolvedModelId = new Map< @@ -7912,6 +7915,7 @@ export class TeamProvisioningService { cwd, providerId, env, + providerArgs, limitContext ); } catch { @@ -7966,7 +7970,7 @@ export class TeamProvisioningService { try { const result = await this.spawnProbe( claudePath, - buildProviderModelProbeArgs(targetModelId), + [...buildProviderModelProbeArgs(targetModelId), ...providerArgs], cwd, env, getProviderModelProbeTimeoutMs(providerId), @@ -8069,13 +8073,18 @@ export class TeamProvisioningService { cwd: string, providerId: TeamProviderId, env: NodeJS.ProcessEnv, + providerArgs: string[] = [], limitContext: boolean ): Promise { - const { stdout } = await execCli(claudePath, ['model', 'list', '--json', '--provider', 'all'], { - cwd, - env, - timeout: 10_000, - }); + const { stdout } = await execCli( + claudePath, + ['model', 'list', '--json', '--provider', 'all', ...providerArgs], + { + cwd, + env, + timeout: 10_000, + } + ); const parsed = extractJsonObjectFromCli(stdout); const defaultModel = parsed.providers?.[providerId]?.defaultModel; const normalizedDefaultModel = @@ -8145,6 +8154,7 @@ export class TeamProvisioningService { params.cwd, providerId, envResolution.env, + envResolution.providerArgs, params.limitContext === true ); const normalized = resolvedDefaultModel?.trim(); @@ -8240,7 +8250,12 @@ export class TeamProvisioningService { const claudePath = await ClaudeBinaryResolver.resolve(); if (!claudePath) return null; - const { env, authSource, warning } = await this.buildProvisioningEnv(providerId); + const { + env, + authSource, + providerArgs = [], + warning, + } = await this.buildProvisioningEnv(providerId); if (warning) { return { claudePath, @@ -8249,7 +8264,7 @@ export class TeamProvisioningService { }; } - const probe = await this.probeClaudeRuntime(claudePath, cwd, env, providerId); + const probe = await this.probeClaudeRuntime(claudePath, cwd, env, providerId, providerArgs); const result = { claudePath, authSource, @@ -18476,7 +18491,8 @@ export class TeamProvisioningService { claudePath: string, cwd: string, env: NodeJS.ProcessEnv, - providerId: TeamProviderId | undefined = 'anthropic' + providerId: TeamProviderId | undefined = 'anthropic', + providerArgs: string[] = [] ): Promise<{ warning?: string }> { const resolvedProviderId = resolveTeamProviderId(providerId); const cliCommandLabel = getConfiguredCliCommandLabel(); @@ -18543,7 +18559,7 @@ export class TeamProvisioningService { try { pingProbe = await this.spawnProbe( claudePath, - getPreflightPingArgs(providerId), + [...getPreflightPingArgs(providerId), ...providerArgs], cwd, env, getPreflightTimeoutMs(providerId), diff --git a/test/main/services/runtime/CliProviderModelAvailabilityService.test.ts b/test/main/services/runtime/CliProviderModelAvailabilityService.test.ts index 552941f5..f0afb968 100644 --- a/test/main/services/runtime/CliProviderModelAvailabilityService.test.ts +++ b/test/main/services/runtime/CliProviderModelAvailabilityService.test.ts @@ -152,4 +152,29 @@ describe('CliProviderModelAvailabilityService', () => { expect(execCliMock).toHaveBeenCalledTimes(3); }); }); + + it('passes provider launch args into codex model probes', async () => { + buildProviderAwareCliEnvMock.mockResolvedValue({ + env: { HOME: '/Users/tester' }, + providerArgs: ['--settings', '{"codex":{"forced_login_method":"chatgpt"}}'], + connectionIssues: {}, + }); + execCliMock.mockResolvedValue({ stdout: 'PONG', stderr: '' }); + + const service = new CliProviderModelAvailabilityService(); + service.getSnapshot(createContext(['gpt-5.4'])); + + await vi.waitFor(() => { + expect(execCliMock).toHaveBeenCalledWith( + '/usr/local/bin/claude', + expect.arrayContaining([ + '--settings', + '{"codex":{"forced_login_method":"chatgpt"}}', + ]), + expect.objectContaining({ + env: { HOME: '/Users/tester' }, + }) + ); + }); + }); }); diff --git a/test/main/services/team/TeamProvisioningServicePrepare.test.ts b/test/main/services/team/TeamProvisioningServicePrepare.test.ts index 9222ad13..5777476a 100644 --- a/test/main/services/team/TeamProvisioningServicePrepare.test.ts +++ b/test/main/services/team/TeamProvisioningServicePrepare.test.ts @@ -1025,6 +1025,47 @@ describe('TeamProvisioningService prepare/auth behavior', () => { expect(result.warning).toContain('request id: req_123'); }); + it('passes provider launch args into codex preflight ping probes', async () => { + const svc = new TeamProvisioningService(); + const spawnProbe = vi + .spyOn(svc as any, 'spawnProbe') + .mockResolvedValueOnce({ + stdout: 'orchestrator-cli 1.2.3', + stderr: '', + exitCode: 0, + }) + .mockResolvedValueOnce({ + stdout: 'PONG', + stderr: '', + exitCode: 0, + }); + + const result = await (svc as any).probeClaudeRuntime( + '/fake/claude', + tempRoot, + { + PATH: '/usr/bin', + SHELL: '/bin/zsh', + }, + 'codex', + ['--settings', '{"codex":{"forced_login_method":"chatgpt"}}'] + ); + + expect(result.warning).toBeUndefined(); + expect(spawnProbe).toHaveBeenNthCalledWith( + 2, + '/fake/claude', + expect.arrayContaining([ + '--settings', + '{"codex":{"forced_login_method":"chatgpt"}}', + ]), + tempRoot, + expect.any(Object), + 60_000, + expect.any(Object) + ); + }); + it('continues selected model verification after transient preflight warnings', async () => { const svc = new TeamProvisioningService(); vi.spyOn(svc as any, 'getCachedOrProbeResult').mockResolvedValue({ @@ -1091,6 +1132,59 @@ describe('TeamProvisioningService prepare/auth behavior', () => { ); }); + it('passes provider launch args into selected codex model probes', async () => { + const svc = new TeamProvisioningService(); + vi.spyOn(svc as any, 'buildProvisioningEnv').mockResolvedValue({ + env: { + PATH: '/usr/bin', + SHELL: '/bin/zsh', + }, + authSource: 'codex_runtime', + geminiRuntimeAuth: null, + providerArgs: ['--settings', '{"codex":{"forced_login_method":"chatgpt"}}'], + }); + const readRuntimeProviderLaunchFacts = vi + .spyOn(svc as any, 'readRuntimeProviderLaunchFacts') + .mockResolvedValue({ + defaultModel: null, + modelIds: new Set(['gpt-5.4']), + modelCatalog: null, + runtimeCapabilities: null, + providerStatus: null, + }); + const spawnProbe = vi.spyOn(svc as any, 'spawnProbe').mockResolvedValue({ + stdout: 'PONG', + stderr: '', + exitCode: 0, + }); + + const result = await (svc as any).verifySelectedProviderModels({ + claudePath: '/fake/claude', + cwd: tempRoot, + providerId: 'codex', + modelIds: ['gpt-5.4'], + limitContext: false, + }); + + expect(result.details).toEqual(['Selected model gpt-5.4 verified for launch.']); + expect(readRuntimeProviderLaunchFacts).toHaveBeenCalledWith( + expect.objectContaining({ + providerArgs: ['--settings', '{"codex":{"forced_login_method":"chatgpt"}}'], + }) + ); + expect(spawnProbe).toHaveBeenCalledWith( + '/fake/claude', + expect.arrayContaining([ + '--settings', + '{"codex":{"forced_login_method":"chatgpt"}}', + ]), + tempRoot, + expect.any(Object), + 60_000, + expect.any(Object) + ); + }); + it('maps ANTHROPIC_AUTH_TOKEN into ANTHROPIC_API_KEY for headless preflight', async () => { const svc = new TeamProvisioningService(); vi.mocked(resolveInteractiveShellEnv).mockResolvedValue({