mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-13 13:22:35 -07:00
fix(core): prevent silent hang during OAuth auth on headless Linux (#26571)
Co-authored-by: Jack Wotherspoon <jackwoth@google.com>
This commit is contained in:
@@ -130,6 +130,24 @@ export async function createContentGeneratorConfig(
|
||||
customHeaders?: Record<string, string>,
|
||||
vertexAiRouting?: VertexAiRoutingConfig,
|
||||
): Promise<ContentGeneratorConfig> {
|
||||
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;
|
||||
|
||||
@@ -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<boolean> {
|
||||
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<boolean> => {
|
||||
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<false>((resolve) =>
|
||||
setTimeout(() => resolve(false), 2000).unref(),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user