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:
Rhys Sullivan
2026-05-06 12:47:30 -07:00
committed by GitHub
parent a38f393af7
commit bb4224fdff
2 changed files with 40 additions and 24 deletions
+18 -16
View File
@@ -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;
+22 -8
View File
@@ -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(),
),
]);
}
/**