google-gemini / google-gemini/gemini-cli
bug: extension update rollback copies an empty temp dir - restores nothing after a failed update
- Dominant language
- TypeScript
- Stars
- 107k
- Forks
- 14.6k
- Avg merge
- 2d 3h
- Merged PRs (30d)
- 45
Description
## What happened?
When an extension update fails, `update.ts` claims to roll back by copying a temp directory over the live extension directory — but that temp directory is created **empty** and nothing is ever written into it. The "rollback" copies zero files; its only observable effect is making the tree writable. Worse, `installOrUpdateExtension` may have already **deleted the old extension directory** before failing, so the extension ends up half-installed/destroyed while the logs claim a rollback happened.
## Affected code
`packages/cli/src/config/extensions/update.ts:102`:
```ts
const tempDir = await ExtensionStorage.createTmpDir(); // created EMPTY
```
`packages/cli/src/config/extensions/update.ts:137-149`:
```ts
} catch (e) {
debugLogger.error(`Error updating extension, rolling back. ${getErrorMessage(e)}`);
dispatchExtensionStateUpdate({ ... ERROR ... });
await copyExtension(tempDir, extension.path); // copies an empty dir
throw e;
} finally {
await fs.promises.rm(tempDir, { recursive: true, force: true });
}
```
`copyExtension` is `fs.promises.cp(source, destination, { recursive: true })` (`extension-manager.ts:1266-1272`). The new version is downloaded into a *different* temp dir inside `installOrUpdateExtension`; this one is never populated.
## How can this be reproduced?
1. Install any extension from GitHub.
2. Force an update failure after uninstall of the previous version (e.g., point at a release asset that 404s mid-flow).
3. Inspect the extension directory: old version gone, no restoration, log says "rolling back".
## What did you expect to happen?
A failed update leaves the previously working version in place.
## Impact
Data loss for the extension: a network blip during `gemini extensions update` can leave it broken until manual reinstall.
## Suggested direction
Snapshot the current extension dir into `tempDir` **before** mutating anything (copy, not move), and restore from it on failure — or make `installOrUpdateExtension` perform its swap atomically (staging dir + rename).
---
*Found by source audit on current `main` (commit `5411f113c`); platform-independent. Related but distinct: #27200/#19013 address transient cleanup failures, not this no-op rollback.*
Contributor guide
Research direction
Start in packages/cli/src/config/extensions/update.ts around lines 102 and 137-149, then trace installOrUpdateExtension and copyExtension in extension-manager.ts:1266-1272. Verify how the current extension is changed or removed before the update fails. Done means a failed update restores the previously working extension instead of copying an empty directory, while the temporary snapshot is cleaned up.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- cli
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 68/100