Skip to content

fix(extensions): back up the extension dir before update so rollback restores it - #29166

Closed
mahirhir wants to merge 1 commit into
google-gemini:mainfrom
mahirhir:fix/extension-update-rollback-backup
Closed

mahirhir wants to merge 1 commit into
google-gemini:mainfrom
mahirhir:fix/extension-update-rollback-backup

Conversation

@mahirhir

@mahirhir mahirhir commented Sep 2, 2026

Copy link
Copy Markdown

Summary

updateExtension never backed up the extension before updating it. It created a temp dir, and on failure copied that temp dir back over extension.path. Nothing ever copied the extension into the temp dir, so the temp dir was always empty and the rollback restored nothing. A failed update left the half-updated installation in place.

Details

packages/cli/src/config/extensions/update.ts. Two changes.

  1. Copy the current installation into the temp dir before anything mutates it.
  2. Track whether that backup succeeded, and only restore from the temp dir when it did. Without the flag, a backup that failed partway would let the catch block copy a partial temp dir over an extension that was still intact, which is the corruption the rollback exists to prevent.

The test change isn't garnish. There was already a rollback test at update.test.ts:278 and it passed against this bug, so the fix can regress silently without it. Its whole rollback assertion was:

expect(copyExtension).toHaveBeenCalledWith('/tmp/mock-dir', mockExtension.path);

copyExtension is mocked as a bare vi.fn() and createTmpDir is stubbed to the string '/tmp/mock-dir'. So it asserted that the restore call was made with the expected arguments. That is equally true when the temp dir is empty. It checked the call, not the effect.

The asymmetry was the tell: there was an assertion for the restore direction and none for the backup direction, and the missing call was exactly the unasserted one. This PR asserts both in order. Order matters, because a backup taken after the install has already mutated the directory is as empty as no backup at all, and an unordered pair wouldn't catch that. A second test covers the new guard.

Related Issues

Fixes #29033

How to Validate

npx vitest run src/config/extensions/update.test.ts   # from packages/cli

15 passed here. For the negative control, revert update.ts alone and re-run: both new tests fail, the other 13 still pass.

Pre-Merge Checklist

  • Added/updated tests (if needed)
  • Updated relevant documentation and README (if needed)
  • Noted breaking changes (if any)

Validated on Windows via npm run only. I couldn't run the full npm run preflight: npm ci fails on this machine building the native tree-sitter-bash dependency, so I installed with --ignore-scripts and ran the single test file plus prettier --check on both changed files. Worth a CI run on the other platforms.

…restores it

updateExtension created a temp dir and, on failure, copied that temp dir
back over the extension. Nothing ever copied the extension into the temp
dir, so the temp dir was always empty and the rollback restored nothing,
leaving the half-updated installation in place.

Copy the current installation into the temp dir before the update runs,
and only restore from it when that backup actually succeeded, so a failed
backup cannot overwrite an intact extension with a partial copy.

The existing rollback test asserted the restore call was made with the
expected arguments, which held equally true when the temp dir was empty,
so it passed against the bug. Assert the backup and restore calls in
order instead, since a backup taken after the mutation is as empty as no
backup at all. Both new assertions fail against the unfixed code.

Fixes google-gemini#29033
@mahirhir
mahirhir requested a review from a team as a code owner September 2, 2026 16:55
@google-cla

google-cla Bot commented Sep 2, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@github-actions github-actions Bot added the size/s A small PR label Sep 2, 2026
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request resolves a critical issue in the extension update logic where rollbacks were ineffective because no backup was created prior to the update. By capturing the current state before any mutations occur and implementing a verification flag, the system now ensures that rollbacks only proceed when a valid backup is available, preventing the corruption of existing installations during failed updates.

Highlights

  • Backup Implementation: Implemented a mandatory backup step for the extension directory before the update process begins to ensure a valid state exists for potential rollbacks.
  • Rollback Safety: Introduced a 'backedUp' flag to prevent the system from attempting to restore from an empty or partial temporary directory if the initial backup fails.
  • Test Coverage: Enhanced unit tests to verify the specific order of operations and added a negative control test case to ensure the system handles failed backups gracefully.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown

📊 PR Size: size/S

  • Lines changed: 45
  • Additions: +43
  • Deletions: -2
  • Files changed: 2

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a backup mechanism before updating an extension, ensuring that a rollback is only attempted if the backup was successfully created. It also updates the test suite to verify the ordering of operations and adds a test case for failed backups. The reviewer suggested a critical improvement to completely remove the corrupted extension directory before restoring the backup to prevent a dirty state, and to wrap the rollback in a try-catch block to avoid shadowing the original error.

Comment on lines +154 to +156
if (backedUp) {
await copyExtension(tempDir, extension.path);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

When rolling back a failed update, copying the backup directory (tempDir) directly over the existing extension.path using copyExtension (which uses fs.promises.cp) will not delete any new files that were successfully written by the failed update before it crashed. This can leave the extension directory in a corrupted, 'dirty' state containing a mix of old and new files.

To ensure a clean and reliable rollback, the corrupted extension.path directory should be completely removed before restoring the backup. Additionally, wrapping the rollback in a try-catch block ensures that if the rollback itself fails, the original installation error e is still thrown and not shadowed by the rollback error.

    if (backedUp) {
      try {
        await fs.promises.rm(extension.path, { recursive: true, force: true });
        await copyExtension(tempDir, extension.path);
      } catch (rollbackError) {
        debugLogger.error(
          'Failed to rollback extension update: ' + getErrorMessage(rollbackError)
        );
      }
    }

@gemini-cli gemini-cli Bot added the area/extensions Issues related to Gemini CLI extensions capability label Sep 2, 2026
@mahirhir

mahirhir commented Sep 2, 2026

Copy link
Copy Markdown
Author

The CLA has been signed. Recheck, please.

@mahirhir

mahirhir commented Sep 3, 2026

Copy link
Copy Markdown
Author

@googlebot I signed it!

@gemini-cli gemini-cli Bot added priority/p2 Important but can be addressed in a future release. area/platform Issues related to Build infra, Release mgmt, Testing, Eval infra, Capacity, Quota mgmt labels Sep 7, 2026
@gemini-cli

gemini-cli Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Hi there! Thank you for your interest in contributing to Gemini CLI.

To ensure we maintain high code quality and focus on our prioritized roadmap, we only guarantee review and consideration of pull requests for issues that are explicitly labeled as 'help wanted'.

This PR will be closed in 7 days if it remains without that designation. We encourage you to find and contribute to existing 'help wanted' issues in our backlog! Thank you for your understanding.

@gemini-cli

gemini-cli Bot commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

This pull request is being closed as it has been open for 14 days without a 'help wanted' designation. We encourage you to find and contribute to existing 'help wanted' issues in our backlog! Thank you for your understanding.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/extensions Issues related to Gemini CLI extensions capability area/platform Issues related to Build infra, Release mgmt, Testing, Eval infra, Capacity, Quota mgmt priority/p2 Important but can be addressed in a future release. size/s A small PR status/pr-nudge-sent

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: extension update rollback copies an empty temp dir - restores nothing after a failed update

1 participant