Skip to content

fix(cli): prevent concurrent extension install races - #29087

Open
nnetraga97 wants to merge 1 commit into
google-gemini:mainfrom
nnetraga97:fix/extension-install-race-29036
Open

fix(cli): prevent concurrent extension install races#29087
nnetraga97 wants to merge 1 commit into
google-gemini:mainfrom
nnetraga97:fix/extension-install-race-29036

Conversation

@nnetraga97

Copy link
Copy Markdown

Summary

Prevent two Gemini CLI processes from installing or updating the same extension at the same time.

Without coordination, both processes can pass the destination check and then interleave file copies and metadata writes. This change uses the existing proper-lockfile dependency to give one process exclusive access to each affected extension name.

Details

  • Acquire the lock after source preparation and consent, but before checking or modifying the destination.
  • Lock both the old and new names during a rename, using a stable order to avoid deadlocks.
  • Keep lock artifacts in ~/.gemini/extension-locks so extension discovery never treats them as installed extensions.
  • Release every acquired lock in the existing cleanup path, including failed installs.
  • Add deterministic coverage for competing installs, updates, renames, failed installs, and extension discovery while a lock is active.

Related Issues

Fixes #29036

How to Validate

npm test -w @google/gemini-cli -- src/config/extension-manager.test.ts src/config/extensions/extensionUpdates.test.ts src/config/extensions/update.test.ts src/commands/extensions/update.test.ts
npm run typecheck -w @google/gemini-cli
npx eslint packages/cli/src/config/extension-manager.ts packages/cli/src/config/extension-manager.test.ts packages/cli/src/config/extensions/storage.ts packages/cli/src/config/extensions/extensionUpdates.test.ts --max-warnings 0

The focused test run passes 46 tests across four files.

Pre-Merge Checklist

  • Documentation changes are not needed for this internal concurrency fix
  • Added regression tests
  • No breaking changes
  • Validated with npm on macOS

@nnetraga97
nnetraga97 requested a review from a team as a code owner August 25, 2026 20:09
@google-cla

google-cla Bot commented Aug 25, 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/l A large sized PR label Aug 25, 2026
@github-actions

Copy link
Copy Markdown

📊 PR Size: size/L

  • Lines changed: 416
  • Additions: +415
  • Deletions: -1
  • Files changed: 4

@gemini-cli gemini-cli Bot added the area/extensions Issues related to Gemini CLI extensions capability label Aug 25, 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 addresses a race condition in the Gemini CLI where concurrent processes could simultaneously modify the same extension directory. By implementing an exclusive locking mechanism, the changes ensure that only one process can perform installation or update operations on a specific extension at any given time. The solution includes robust cleanup of lock files and handles edge cases like renames and failed operations to maintain system stability.

Highlights

  • Concurrency Control: Introduced file-based locking using proper-lockfile to prevent race conditions when multiple Gemini CLI processes attempt to install or update the same extension simultaneously.
  • Lock Management: Implemented a centralized lock directory at ~/.gemini/extension-locks to manage exclusive access, ensuring that lock artifacts do not interfere with extension discovery.
  • Deadlock Prevention: Added logic to acquire locks in a stable, sorted order during extension rename operations to prevent potential deadlocks.
  • Regression Testing: Added comprehensive test cases to simulate concurrent installation, updates, renames, and failed installation scenarios.
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.

@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 concurrent installation locking for extensions using the proper-lockfile library to prevent race conditions during installations, updates, or renames. It also adds comprehensive tests to verify this behavior. A critical issue was identified where locking a non-existent extension directory during a new installation will throw an ENOENT error; it is recommended to create and lock a stable placeholder file inside the locks directory instead.

Comment on lines +1275 to +1287
for (const extensionName of uniqueNames) {
const extensionPath = new ExtensionStorage(
extensionName,
).getExtensionDir();
releases.push(
await lock(extensionPath, {
realpath: false,
// Keep lock directories outside the extensions directory because the
// loader treats every entry there as a potential extension.
lockfilePath: path.join(locksDir, `${extensionName}.lock`),
}),
);
}

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.

critical

The proper-lockfile library requires the target path to exist before attempting to lock it. When installing a new extension, the directory returned by new ExtensionStorage(extensionName).getExtensionDir() does not exist yet, which will cause lock() to throw an ENOENT error and fail the installation.

To resolve this, we should create and lock a stable placeholder file inside the locksDir (which is guaranteed to exist since we call mkdir on it) instead of locking the non-existent extension directory.

    for (const extensionName of uniqueNames) {
      const lockTarget = path.join(locksDir, extensionName);
      await fs.promises.writeFile(lockTarget, '', { flag: 'a' });
      releases.push(
        await lock(lockTarget, {
          realpath: false,
          // Keep lock directories outside the extensions directory because the
          // loader treats every entry there as a potential extension.
          lockfilePath: path.join(locksDir, extensionName + '.lock'),
        }),
      );
    }

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 size/l A large sized PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: concurrent extension installs race past the existence check and corrupt each other's destination

1 participant