diff --git a/packages/core/src/core/contentGenerator.ts b/packages/core/src/core/contentGenerator.ts index 040fed0e9a..bcee8cfef4 100644 --- a/packages/core/src/core/contentGenerator.ts +++ b/packages/core/src/core/contentGenerator.ts @@ -130,6 +130,24 @@ export async function createContentGeneratorConfig( customHeaders?: Record, vertexAiRouting?: VertexAiRoutingConfig, ): Promise { + const contentGeneratorConfig: ContentGeneratorConfig = { + authType, + proxy: config?.getProxy(), + baseUrl, + customHeaders, + vertexAiRouting, + }; + + // If we are using Google auth or we are in Cloud Shell, there is nothing else to validate for now. + // Return before touching the API-key keychain: on Linux without a Secret Service + // (WSL/SSH/Docker/CI) keytar can block indefinitely on its functional probe. + if ( + authType === AuthType.LOGIN_WITH_GOOGLE || + authType === AuthType.COMPUTE_ADC + ) { + return contentGeneratorConfig; + } + const geminiApiKey = apiKey || process.env['GEMINI_API_KEY'] || @@ -142,22 +160,6 @@ export async function createContentGeneratorConfig( undefined; const googleCloudLocation = process.env['GOOGLE_CLOUD_LOCATION'] || undefined; - const contentGeneratorConfig: ContentGeneratorConfig = { - authType, - proxy: config?.getProxy(), - baseUrl, - customHeaders, - vertexAiRouting, - }; - - // If we are using Google auth or we are in Cloud Shell, there is nothing else to validate for now - if ( - authType === AuthType.LOGIN_WITH_GOOGLE || - authType === AuthType.COMPUTE_ADC - ) { - return contentGeneratorConfig; - } - if (authType === AuthType.USE_GEMINI && geminiApiKey) { contentGeneratorConfig.apiKey = geminiApiKey; contentGeneratorConfig.vertexai = false; diff --git a/packages/core/src/services/keychainService.ts b/packages/core/src/services/keychainService.ts index 0d12e68ffb..2227166aec 100644 --- a/packages/core/src/services/keychainService.ts +++ b/packages/core/src/services/keychainService.ts @@ -140,7 +140,7 @@ export class KeychainService { return keychainModule; } - debugLogger.debug('Keychain functional verification failed'); + debugLogger.debug('Keychain functional verification failed or timed out'); return null; } catch (error) { // Avoid logging full error objects to prevent PII exposure. @@ -173,18 +173,32 @@ export class KeychainService { } // Performs a set-get-delete cycle to verify keychain functionality. + // Capped with a 2s timeout so a non-responsive Secret Service (common on + // headless Linux: WSL/SSH/Docker without gnome-keyring or D-Bus) falls back + // to FileKeychain instead of hanging the CLI indefinitely. private async isKeychainFunctional(keychain: Keychain): Promise { const testAccount = `${KEYCHAIN_TEST_PREFIX}${crypto.randomBytes(8).toString('hex')}`; const testPassword = 'test'; - await keychain.setPassword(this.serviceName, testAccount, testPassword); - const retrieved = await keychain.getPassword(this.serviceName, testAccount); - const deleted = await keychain.deletePassword( - this.serviceName, - testAccount, - ); + const probe = async (): Promise => { + await keychain.setPassword(this.serviceName, testAccount, testPassword); + const retrieved = await keychain.getPassword( + this.serviceName, + testAccount, + ); + const deleted = await keychain.deletePassword( + this.serviceName, + testAccount, + ); + return deleted && retrieved === testPassword; + }; - return deleted && retrieved === testPassword; + return Promise.race([ + probe(), + new Promise((resolve) => + setTimeout(() => resolve(false), 2000).unref(), + ), + ]); } /**