From 09ef33ec3a5ea9b9d09ff0a43c6013f66bcc4641 Mon Sep 17 00:00:00 2001 From: Sandy Tao Date: Fri, 10 Oct 2025 16:50:54 -0700 Subject: [PATCH] fix(cli): prioritize configured auth over env vars in non-interactive mode (#10935) --- integration-tests/json-output.test.ts | 6 +-- .../src/validateNonInterActiveAuth.test.ts | 51 ++++++++++++------- .../cli/src/validateNonInterActiveAuth.ts | 18 +++---- 3 files changed, 43 insertions(+), 32 deletions(-) diff --git a/integration-tests/json-output.test.ts b/integration-tests/json-output.test.ts index 70d2955536..a3a006de5a 100644 --- a/integration-tests/json-output.test.ts +++ b/integration-tests/json-output.test.ts @@ -80,11 +80,9 @@ describe('JSON output', () => { expect(payload.error.type).toBe('Error'); expect(payload.error.code).toBe(1); expect(payload.error.message).toContain( - 'configured auth type is gemini-api-key', - ); - expect(payload.error.message).toContain( - 'current auth type is oauth-personal', + "enforced authentication type is 'gemini-api-key'", ); + expect(payload.error.message).toContain("current type is 'oauth-personal'"); }); it('should not exit on tool errors and allow model to self-correct in JSON mode', async () => { diff --git a/packages/cli/src/validateNonInterActiveAuth.test.ts b/packages/cli/src/validateNonInterActiveAuth.test.ts index e4f9d961f5..9c570da500 100644 --- a/packages/cli/src/validateNonInterActiveAuth.test.ts +++ b/packages/cli/src/validateNonInterActiveAuth.test.ts @@ -209,19 +209,18 @@ describe('validateNonInterActiveAuth', () => { expect(refreshAuthMock).toHaveBeenCalledWith(AuthType.USE_GEMINI); }); - it('uses configuredAuthType if provided', async () => { - // Set required env var for USE_GEMINI + it('uses configuredAuthType over environment variables', async () => { process.env['GEMINI_API_KEY'] = 'fake-key'; const nonInteractiveConfig = { refreshAuth: refreshAuthMock, }; await validateNonInteractiveAuth( - AuthType.USE_GEMINI, + AuthType.LOGIN_WITH_GOOGLE, undefined, nonInteractiveConfig, mockSettings, ); - expect(refreshAuthMock).toHaveBeenCalledWith(AuthType.USE_GEMINI); + expect(refreshAuthMock).toHaveBeenCalledWith(AuthType.LOGIN_WITH_GOOGLE); }); it('exits if validateAuthMethod returns error', async () => { @@ -274,16 +273,14 @@ describe('validateNonInterActiveAuth', () => { expect(refreshAuthMock).toHaveBeenCalledWith('invalid-auth-type'); }); - it('uses enforcedAuthType if provided', async () => { + it('succeeds if effectiveAuthType matches enforcedAuthType', async () => { mockSettings.merged.security.auth.enforcedType = AuthType.USE_GEMINI; - mockSettings.merged.security.auth.selectedType = AuthType.USE_GEMINI; - // Set required env var for USE_GEMINI to ensure enforcedAuthType takes precedence process.env['GEMINI_API_KEY'] = 'fake-key'; const nonInteractiveConfig = { refreshAuth: refreshAuthMock, }; await validateNonInteractiveAuth( - AuthType.USE_GEMINI, + undefined, undefined, nonInteractiveConfig, mockSettings, @@ -291,15 +288,11 @@ describe('validateNonInterActiveAuth', () => { expect(refreshAuthMock).toHaveBeenCalledWith(AuthType.USE_GEMINI); }); - it('exits if currentAuthType does not match enforcedAuthType', async () => { + it('exits if configuredAuthType does not match enforcedAuthType', async () => { mockSettings.merged.security.auth.enforcedType = AuthType.LOGIN_WITH_GOOGLE; - process.env['GOOGLE_GENAI_USE_VERTEXAI'] = 'true'; const nonInteractiveConfig = { refreshAuth: refreshAuthMock, getOutputFormat: vi.fn().mockReturnValue(OutputFormat.TEXT), - getContentGeneratorConfig: vi - .fn() - .mockReturnValue({ authType: undefined }), }; try { await validateNonInteractiveAuth( @@ -313,7 +306,31 @@ describe('validateNonInterActiveAuth', () => { expect((e as Error).message).toContain('process.exit(1) called'); } expect(consoleErrorSpy).toHaveBeenCalledWith( - 'The configured auth type is oauth-personal, but the current auth type is vertex-ai. Please re-authenticate with the correct type.', + "The enforced authentication type is 'oauth-personal', but the current type is 'gemini-api-key'. Please re-authenticate with the correct type.", + ); + expect(processExitSpy).toHaveBeenCalledWith(1); + }); + + it('exits if auth from env var does not match enforcedAuthType', async () => { + mockSettings.merged.security.auth.enforcedType = AuthType.LOGIN_WITH_GOOGLE; + process.env['GEMINI_API_KEY'] = 'fake-key'; + const nonInteractiveConfig = { + refreshAuth: refreshAuthMock, + getOutputFormat: vi.fn().mockReturnValue(OutputFormat.TEXT), + }; + try { + await validateNonInteractiveAuth( + undefined, + undefined, + nonInteractiveConfig, + mockSettings, + ); + expect.fail('Should have exited'); + } catch (e) { + expect((e as Error).message).toContain('process.exit(1) called'); + } + expect(consoleErrorSpy).toHaveBeenCalledWith( + "The enforced authentication type is 'oauth-personal', but the current type is 'gemini-api-key'. Please re-authenticate with the correct type.", ); expect(processExitSpy).toHaveBeenCalledWith(1); }); @@ -352,8 +369,6 @@ describe('validateNonInterActiveAuth', () => { it('prints JSON error when enforced auth mismatches current auth and exits with code 1', async () => { mockSettings.merged.security.auth.enforcedType = AuthType.USE_GEMINI; - process.env['GOOGLE_GENAI_USE_GCA'] = 'true'; - const nonInteractiveConfig = { refreshAuth: refreshAuthMock, getOutputFormat: vi.fn().mockReturnValue(OutputFormat.JSON), @@ -365,7 +380,7 @@ describe('validateNonInterActiveAuth', () => { let thrown: Error | undefined; try { await validateNonInteractiveAuth( - undefined, + AuthType.LOGIN_WITH_GOOGLE, undefined, nonInteractiveConfig as unknown as Config, mockSettings, @@ -381,7 +396,7 @@ describe('validateNonInterActiveAuth', () => { expect(payload.error.type).toBe('Error'); expect(payload.error.code).toBe(1); expect(payload.error.message).toContain( - 'The configured auth type is gemini-api-key, but the current auth type is oauth-personal.', + "The enforced authentication type is 'gemini-api-key', but the current type is 'oauth-personal'. Please re-authenticate with the correct type.", ); } }); diff --git a/packages/cli/src/validateNonInterActiveAuth.ts b/packages/cli/src/validateNonInterActiveAuth.ts index 9c46766ac9..54c7e4cc1e 100644 --- a/packages/cli/src/validateNonInterActiveAuth.ts +++ b/packages/cli/src/validateNonInterActiveAuth.ts @@ -31,17 +31,15 @@ export async function validateNonInteractiveAuth( settings: LoadedSettings, ) { try { - const enforcedType = settings.merged.security?.auth?.enforcedType; - if (enforcedType) { - const currentAuthType = getAuthTypeFromEnv(); - if (currentAuthType !== enforcedType) { - const message = `The configured auth type is ${enforcedType}, but the current auth type is ${currentAuthType}. Please re-authenticate with the correct type.`; - throw new Error(message); - } - } + const effectiveAuthType = configuredAuthType || getAuthTypeFromEnv(); - const effectiveAuthType = - enforcedType || getAuthTypeFromEnv() || configuredAuthType; + const enforcedType = settings.merged.security?.auth?.enforcedType; + if (enforcedType && effectiveAuthType !== enforcedType) { + const message = effectiveAuthType + ? `The enforced authentication type is '${enforcedType}', but the current type is '${effectiveAuthType}'. Please re-authenticate with the correct type.` + : `The auth type '${enforcedType}' is enforced, but no authentication is configured.`; + throw new Error(message); + } if (!effectiveAuthType) { const message = `Please set an Auth method in your ${USER_SETTINGS_PATH} or specify one of the following environment variables before running: GEMINI_API_KEY, GOOGLE_GENAI_USE_VERTEXAI, GOOGLE_GENAI_USE_GCA`;