From bdd15e8911bac67f96624724150f1af259d00600 Mon Sep 17 00:00:00 2001 From: Tommaso Sciortino Date: Fri, 5 Dec 2025 14:12:54 -0800 Subject: [PATCH] Fully detach autoupgrade process (#14595) --- .../cli/src/utils/handleAutoUpdate.test.ts | 22 ++++++------------- packages/cli/src/utils/handleAutoUpdate.ts | 12 +++++----- 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/packages/cli/src/utils/handleAutoUpdate.test.ts b/packages/cli/src/utils/handleAutoUpdate.test.ts index 7d6b7c612a..d7134d44db 100644 --- a/packages/cli/src/utils/handleAutoUpdate.test.ts +++ b/packages/cli/src/utils/handleAutoUpdate.test.ts @@ -11,6 +11,7 @@ import { updateEventEmitter } from './updateEventEmitter.js'; import type { UpdateObject } from '../ui/utils/updateCheck.js'; import type { LoadedSettings } from '../config/settings.js'; import EventEmitter from 'node:events'; +import type { ChildProcess } from 'node:child_process'; import { handleAutoUpdate, setUpdateHandler } from './handleAutoUpdate.js'; import { MessageType } from '../ui/types.js'; @@ -26,22 +27,13 @@ vi.mock('./updateEventEmitter.js', async (importOriginal) => importOriginal(), ); -interface MockChildProcess extends EventEmitter { - stdin: EventEmitter & { - write: Mock; - end: Mock; - }; - stderr: EventEmitter; -} - const mockGetInstallationInfo = vi.mocked(getInstallationInfo); -// updateEventEmitter is now real, but we will spy on it describe('handleAutoUpdate', () => { let mockSpawn: Mock; let mockUpdateInfo: UpdateObject; let mockSettings: LoadedSettings; - let mockChildProcess: MockChildProcess; + let mockChildProcess: ChildProcess; beforeEach(() => { mockSpawn = vi.fn(); @@ -70,8 +62,8 @@ describe('handleAutoUpdate', () => { write: vi.fn(), end: vi.fn(), }), - stderr: new EventEmitter(), - }) as MockChildProcess; + unref: vi.fn(), + }) as unknown as ChildProcess; mockSpawn.mockReturnValue( mockChildProcess as unknown as ReturnType, @@ -194,7 +186,6 @@ describe('handleAutoUpdate', () => { // Simulate failed execution setTimeout(() => { - mockChildProcess.stderr.emit('data', 'An error occurred'); mockChildProcess.emit('close', 1); resolve(); }, 0); @@ -204,7 +195,7 @@ describe('handleAutoUpdate', () => { expect(updateEventEmitter.emit).toHaveBeenCalledWith('update-failed', { message: - 'Automatic update failed. Please try updating manually. (command: npm i -g @google/gemini-cli@2.0.0, stderr: An error occurred)', + 'Automatic update failed. Please try updating manually. (command: npm i -g @google/gemini-cli@2.0.0)', }); }); @@ -253,7 +244,8 @@ describe('handleAutoUpdate', () => { 'npm i -g @google/gemini-cli@nightly', { shell: true, - stdio: 'pipe', + stdio: 'ignore', + detached: true, }, ); }); diff --git a/packages/cli/src/utils/handleAutoUpdate.ts b/packages/cli/src/utils/handleAutoUpdate.ts index ac2540af04..b1940c3037 100644 --- a/packages/cli/src/utils/handleAutoUpdate.ts +++ b/packages/cli/src/utils/handleAutoUpdate.ts @@ -68,11 +68,13 @@ export function handleAutoUpdate( '@latest', isNightly ? '@nightly' : `@${info.update.latest}`, ); - const updateProcess = spawnFn(updateCommand, { stdio: 'pipe', shell: true }); - let errorOutput = ''; - updateProcess.stderr.on('data', (data) => { - errorOutput += data.toString(); + const updateProcess = spawnFn(updateCommand, { + stdio: 'ignore', + shell: true, + detached: true, }); + // Un-reference the child process to allow the parent to exit independently. + updateProcess.unref(); updateProcess.on('close', (code) => { if (code === 0) { @@ -82,7 +84,7 @@ export function handleAutoUpdate( }); } else { updateEventEmitter.emit('update-failed', { - message: `Automatic update failed. Please try updating manually. (command: ${updateCommand}, stderr: ${errorOutput.trim()})`, + message: `Automatic update failed. Please try updating manually. (command: ${updateCommand})`, }); } });