Skip to content

Commit d743c6f

Browse files
authored
fix: suppress duplicate extension warnings during startup (google-gemini#26208)
1 parent a15568e commit d743c6f

3 files changed

Lines changed: 79 additions & 16 deletions

File tree

packages/cli/src/config/config.ts

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import {
3737
getAdminErrorMessage,
3838
isHeadlessMode,
3939
Config,
40+
SimpleExtensionLoader,
4041
resolveToRealPath,
4142
applyAdminAllowlist,
4243
applyRequiredServers,
@@ -558,6 +559,7 @@ export interface LoadCliConfigOptions {
558559
disabled?: string[];
559560
};
560561
worktreeSettings?: WorktreeSettings;
562+
skipExtensions?: boolean;
561563
}
562564

563565
export async function loadCliConfig(
@@ -566,7 +568,7 @@ export async function loadCliConfig(
566568
argv: CliArgs,
567569
options: LoadCliConfigOptions = {},
568570
): Promise<Config> {
569-
const { cwd = process.cwd(), projectHooks } = options;
571+
const { cwd = process.cwd(), projectHooks, skipExtensions = false } = options;
570572
const debugMode = isDebugMode(argv);
571573

572574
const worktreeSettings =
@@ -641,21 +643,24 @@ export async function loadCliConfig(
641643
includeDirectories.push(...ideFolders);
642644
}
643645

644-
const extensionManager = new ExtensionManager({
645-
settings,
646-
requestConsent: requestConsentNonInteractive,
647-
requestSetting: promptForSetting,
648-
workspaceDir: cwd,
649-
enabledExtensionOverrides: argv.extensions,
650-
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
651-
eventEmitter: coreEvents as EventEmitter<ExtensionEvents>,
652-
clientVersion: await getVersion(),
653-
});
654-
await extensionManager.loadExtensions();
646+
let extensionManager: ExtensionManager | undefined;
647+
if (!skipExtensions) {
648+
extensionManager = new ExtensionManager({
649+
settings,
650+
requestConsent: requestConsentNonInteractive,
651+
requestSetting: promptForSetting,
652+
workspaceDir: cwd,
653+
enabledExtensionOverrides: argv.extensions,
654+
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
655+
eventEmitter: coreEvents as EventEmitter<ExtensionEvents>,
656+
clientVersion: await getVersion(),
657+
});
658+
await extensionManager.loadExtensions();
659+
}
655660

656661
const extensionPlanSettings = extensionManager
657-
.getExtensions()
658-
.find((ext) => ext.isActive && ext.plan?.directory)?.plan;
662+
?.getExtensions()
663+
?.find((ext) => ext.isActive && ext.plan?.directory)?.plan;
659664

660665
const experimentalJitContext = settings.experimental.jitContext ?? true;
661666

@@ -673,6 +678,9 @@ export async function loadCliConfig(
673678
let fileCount = 0;
674679
let filePaths: string[] = [];
675680

681+
const finalExtensionLoader =
682+
extensionManager ?? new SimpleExtensionLoader([]);
683+
676684
if (!experimentalJitContext) {
677685
// Call the (now wrapper) loadHierarchicalGeminiMemory which calls the server's version
678686
const result = await loadServerHierarchicalMemory(
@@ -681,7 +689,7 @@ export async function loadCliConfig(
681689
? includeDirectories
682690
: [],
683691
fileService,
684-
extensionManager,
692+
finalExtensionLoader,
685693
trustedFolder,
686694
memoryImportFormat,
687695
memoryFileFiltering,
@@ -1037,7 +1045,7 @@ export async function loadCliConfig(
10371045
listSessions: argv.listSessions || false,
10381046
deleteSession: argv.deleteSession,
10391047
enabledExtensions: argv.extensions,
1040-
extensionLoader: extensionManager,
1048+
extensionLoader: finalExtensionLoader,
10411049
extensionRegistryURI,
10421050
enableExtensionReloading: settings.experimental?.extensionReloading,
10431051
enableAgents: settings.experimental?.enableAgents,
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* @license
3+
* Copyright 2026 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import { describe, it, expect, vi, beforeEach } from 'vitest';
8+
import { loadCliConfig, type CliArgs } from './config.js';
9+
import { ExtensionManager } from './extension-manager.js';
10+
import { createTestMergedSettings } from './settings.js';
11+
12+
vi.mock('./extension-manager.js', () => ({
13+
ExtensionManager: vi.fn().mockImplementation(() => ({
14+
loadExtensions: vi.fn().mockResolvedValue([]),
15+
getExtensions: vi.fn().mockReturnValue([]),
16+
})),
17+
}));
18+
19+
describe('loadCliConfig skipExtensions', () => {
20+
const settings = createTestMergedSettings();
21+
const argv = {
22+
query: undefined,
23+
model: undefined,
24+
sandbox: undefined,
25+
debug: undefined,
26+
prompt: undefined,
27+
promptInteractive: undefined,
28+
yolo: undefined,
29+
approvalMode: undefined,
30+
policy: undefined,
31+
adminPolicy: undefined,
32+
allowedMcpServerNames: undefined,
33+
allowedTools: undefined,
34+
extensions: undefined,
35+
listExtensions: undefined,
36+
resume: undefined,
37+
sessionId: undefined,
38+
listSessions: undefined,
39+
} as unknown as CliArgs;
40+
41+
beforeEach(() => {
42+
vi.clearAllMocks();
43+
});
44+
45+
it('should load extensions by default', async () => {
46+
await loadCliConfig(settings, 'session-id', argv);
47+
expect(ExtensionManager).toHaveBeenCalled();
48+
});
49+
50+
it('should skip extensions when skipExtensions is true', async () => {
51+
await loadCliConfig(settings, 'session-id', argv, { skipExtensions: true });
52+
expect(ExtensionManager).not.toHaveBeenCalled();
53+
});
54+
});

packages/cli/src/gemini.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,7 @@ export async function main() {
409409

410410
const partialConfig = await loadCliConfig(settings.merged, sessionId, argv, {
411411
projectHooks: settings.workspace.settings.hooks,
412+
skipExtensions: true,
412413
});
413414

414415
adminControlsListner.setConfig(partialConfig);

0 commit comments

Comments
 (0)