Fix handling of empty settings (#18131)

This commit is contained in:
christine betts
2026-02-03 15:39:20 -05:00
committed by GitHub
parent 2cf3a14439
commit 3e954930f1
9 changed files with 83 additions and 13 deletions

View File

@@ -8,7 +8,10 @@ import { ExtensionManager } from '../../config/extension-manager.js';
import { promptForSetting } from '../../config/extensions/extensionSettings.js';
import { loadSettings } from '../../config/settings.js';
import { requestConsentNonInteractive } from '../../config/extensions/consent.js';
import { debugLogger } from '@google/gemini-cli-core';
import {
debugLogger,
type ResolvedExtensionSetting,
} from '@google/gemini-cli-core';
export async function getExtensionManager() {
const workspaceDir = process.cwd();
@@ -35,3 +38,15 @@ export async function getExtensionAndManager(name: string) {
return { extension, extensionManager };
}
export function getFormattedSettingValue(
setting: ResolvedExtensionSetting,
): string {
if (!setting.value) {
return '[not set]';
}
if (setting.sensitive) {
return '***';
}
return setting.value;
}

View File

@@ -195,7 +195,7 @@ describe('ExtensionManager Settings Scope', () => {
(s) => s.envVar === 'TEST_SETTING',
);
expect(setting).toBeDefined();
expect(setting?.value).toBe('[not set]');
expect(setting?.value).toBeUndefined();
expect(setting?.scope).toBeUndefined();
// Verify output string does not contain scope

View File

@@ -70,6 +70,7 @@ import {
} from './extensions/extensionSettings.js';
import type { EventEmitter } from 'node:stream';
import { themeManager } from '../ui/themes/theme-manager.js';
import { getFormattedSettingValue } from '../commands/extensions/utils.js';
interface ExtensionManagerParams {
enabledExtensionOverrides?: string[];
@@ -648,12 +649,7 @@ Would you like to attempt to install via "git clone" instead?`,
resolvedSettings.push({
name: setting.name,
envVar: setting.envVar,
value:
value === undefined
? '[not set]'
: setting.sensitive
? '***'
: value,
value,
sensitive: setting.sensitive ?? false,
scope,
source,
@@ -941,7 +937,7 @@ Would you like to attempt to install via "git clone" instead?`,
}
scope += ')';
}
output += `\n ${setting.name}: ${setting.value} ${scope}`;
output += `\n ${setting.name}: ${getFormattedSettingValue(setting)} ${scope}`;
});
}
return output;

View File

@@ -786,6 +786,23 @@ describe('extensionSettings', () => {
expect(await userKeychain.getSecret('VAR2')).toBeNull();
});
it('should delete a non-sensitive setting if the new value is empty', async () => {
mockRequestSetting.mockResolvedValue('');
await updateSetting(
config,
'12345',
'VAR1',
mockRequestSetting,
ExtensionSettingScope.USER,
tempWorkspaceDir,
);
const expectedEnvPath = path.join(extensionDir, '.env');
const actualContent = await fsPromises.readFile(expectedEnvPath, 'utf-8');
expect(actualContent).not.toContain('VAR1=');
});
it('should not throw if deleting a non-existent sensitive setting with empty value', async () => {
mockRequestSetting.mockResolvedValue('');
// Ensure it doesn't exist first

View File

@@ -251,7 +251,11 @@ export async function updateSetting(
}
const parsedEnv = dotenv.parse(envContent);
parsedEnv[settingToUpdate.envVar] = newValue;
if (!newValue) {
delete parsedEnv[settingToUpdate.envVar];
} else {
parsedEnv[settingToUpdate.envVar] = newValue;
}
// We only want to write back the variables that are not sensitive.
const nonSensitiveSettings: Record<string, string> = {};

View File

@@ -9,6 +9,7 @@ import { Box, Text } from 'ink';
import { useUIState } from '../../contexts/UIStateContext.js';
import { ExtensionUpdateState } from '../../state/extensions.js';
import { debugLogger, type GeminiCLIExtension } from '@google/gemini-cli-core';
import { getFormattedSettingValue } from '../../../commands/extensions/utils.js';
interface ExtensionsList {
extensions: readonly GeminiCLIExtension[];
@@ -70,7 +71,7 @@ export const ExtensionsList: React.FC<ExtensionsList> = ({ extensions }) => {
<Text>settings:</Text>
{ext.resolvedSettings.map((setting) => (
<Text key={setting.name}>
- {setting.name}: {setting.value}
- {setting.name}: {getFormattedSettingValue(setting)}
{setting.scope && (
<Text color="gray">
{' '}