From defda3a97dc5257cbc4edd0b1a484cee65e15b3f Mon Sep 17 00:00:00 2001 From: Jacob MacDonald Date: Thu, 25 Sep 2025 10:58:43 -0700 Subject: [PATCH] Fix duplicate info messages for extension updates (#9760) --- packages/cli/src/config/extensions/update.ts | 20 ++++----- .../src/ui/hooks/useExtensionUpdates.test.ts | 2 +- .../cli/src/ui/hooks/useExtensionUpdates.ts | 43 +++++++++++-------- 3 files changed, 35 insertions(+), 30 deletions(-) diff --git a/packages/cli/src/config/extensions/update.ts b/packages/cli/src/config/extensions/update.ts index 33b560fe3e..bea95428ae 100644 --- a/packages/cli/src/config/extensions/update.ts +++ b/packages/cli/src/config/extensions/update.ts @@ -143,17 +143,17 @@ export async function checkForAllExtensionUpdates( >, cwd: string = process.cwd(), ): Promise> { + let newStates: Map = new Map( + extensionsUpdateState, + ); for (const extension of extensions) { const initialState = extensionsUpdateState.get(extension.name); if (initialState === undefined) { if (!extension.installMetadata) { setExtensionsUpdateState((prev) => { - extensionsUpdateState = new Map(prev); - extensionsUpdateState.set( - extension.name, - ExtensionUpdateState.NOT_UPDATABLE, - ); - return extensionsUpdateState; + newStates = new Map(prev); + newStates.set(extension.name, ExtensionUpdateState.NOT_UPDATABLE); + return newStates; }); continue; } @@ -161,14 +161,14 @@ export async function checkForAllExtensionUpdates( extension, (updatedState) => { setExtensionsUpdateState((prev) => { - extensionsUpdateState = new Map(prev); - extensionsUpdateState.set(extension.name, updatedState); - return extensionsUpdateState; + newStates = new Map(prev); + newStates.set(extension.name, updatedState); + return newStates; }); }, cwd, ); } } - return extensionsUpdateState; + return newStates; } diff --git a/packages/cli/src/ui/hooks/useExtensionUpdates.test.ts b/packages/cli/src/ui/hooks/useExtensionUpdates.test.ts index 5f621385bf..ed783c7954 100644 --- a/packages/cli/src/ui/hooks/useExtensionUpdates.test.ts +++ b/packages/cli/src/ui/hooks/useExtensionUpdates.test.ts @@ -143,7 +143,7 @@ describe('useExtensionUpdates', () => { expect(addItem).toHaveBeenCalledWith( { type: MessageType.INFO, - text: 'Extension test-extension has an update available, run "/extensions update test-extension" to install it.', + text: 'You have 1 extension with an update available, run "/extensions list" for more information.', }, expect.any(Number), ); diff --git a/packages/cli/src/ui/hooks/useExtensionUpdates.ts b/packages/cli/src/ui/hooks/useExtensionUpdates.ts index b8db0e4849..70f77bf15d 100644 --- a/packages/cli/src/ui/hooks/useExtensionUpdates.ts +++ b/packages/cli/src/ui/hooks/useExtensionUpdates.ts @@ -7,7 +7,7 @@ import type { GeminiCLIExtension } from '@google/gemini-cli-core'; import { getErrorMessage } from '../../utils/errors.js'; import { ExtensionUpdateState } from '../state/extensions.js'; -import { useMemo, useState } from 'react'; +import { useState } from 'react'; import type { UseHistoryManagerReturn } from './useHistoryManager.js'; import { MessageType } from '../types.js'; import { @@ -24,13 +24,18 @@ export const useExtensionUpdates = ( const [extensionsUpdateState, setExtensionsUpdateState] = useState( new Map(), ); - useMemo(() => { - const checkUpdates = async () => { + const [isChecking, setIsChecking] = useState(false); + + (async () => { + if (isChecking) return; + setIsChecking(true); + try { const updateState = await checkForAllExtensionUpdates( extensions, extensionsUpdateState, setExtensionsUpdateState, ); + let extensionsWithUpdatesCount = 0; for (const extension of extensions) { const prevState = extensionsUpdateState.get(extension.name); const currentState = updateState.get(extension.name); @@ -70,24 +75,24 @@ export const useExtensionUpdates = ( ); }); } else { - addItem( - { - type: MessageType.INFO, - text: `Extension ${extension.name} has an update available, run "/extensions update ${extension.name}" to install it.`, - }, - Date.now(), - ); + extensionsWithUpdatesCount++; } } - }; - checkUpdates(); - }, [ - extensions, - extensionsUpdateState, - setExtensionsUpdateState, - addItem, - cwd, - ]); + if (extensionsWithUpdatesCount > 0) { + const s = extensionsWithUpdatesCount > 1 ? 's' : ''; + addItem( + { + type: MessageType.INFO, + text: `You have ${extensionsWithUpdatesCount} extension${s} with an update available, run "/extensions list" for more information.`, + }, + Date.now(), + ); + } + } finally { + setIsChecking(false); + } + })(); + return { extensionsUpdateState, setExtensionsUpdateState,