Skip to content

fix(ci): retry staging-tmp dist-tag removal after npm publish#28534

Open
IshankDev wants to merge 1 commit into
google-gemini:mainfrom
IshankDev:contrib/28533
Open

fix(ci): retry staging-tmp dist-tag removal after npm publish#28534
IshankDev wants to merge 1 commit into
google-gemini:mainfrom
IshankDev:contrib/28533

Conversation

@IshankDev

Copy link
Copy Markdown

Summary

  • Nightly release #28533 failed because Wombat/npm acknowledged publishing the large @google/gemini-cli-core package before the staging-tmp dist-tag was queryable, so immediate npm dist-tag rm staging-tmp exited with staging-tmp is not a dist-tag.
  • Add scripts/remove-npm-dist-tag.js to retry removal until the tag propagates (failing fast on unrelated errors), and use it for core/CLI/a2a publish steps.
  • Includes unit coverage for success, retry, non-retryable errors, and exhausted retries.

Fixes #28533

Test plan

  • npx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/remove-npm-dist-tag.test.js
  • Re-run or wait for the next scheduled nightly release and confirm Publish Release gets past core staging-tmp removal
  • Confirm leftover staging-tmp from the failed run is cleaned up once a successful publish completes (or cleaned manually if needed)

Made with Cursor

@IshankDev
IshankDev requested a review from a team as a code owner July 25, 2026 18:02
@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 intermittent CI failures during the nightly release process caused by npm registry propagation delays. By implementing a retry mechanism for the removal of 'staging-tmp' dist-tags, the release pipeline is now more resilient to transient errors that occur when attempting to clean up tags immediately after a package publish.

Highlights

  • Robust npm dist-tag removal: Introduced a new utility script to handle retries when removing npm dist-tags, addressing race conditions where tags are not yet queryable immediately after publishing large packages.
  • CI workflow updates: Updated the publish-release GitHub Action to utilize the new retry logic for core, CLI, and a2a package publishing steps.
  • Comprehensive testing: Added unit tests covering successful removal, retry logic for propagation delays, handling of non-retryable errors, and failure after exhausted retries.
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 added the size/l A large sized PR label Jul 25, 2026
@github-actions

github-actions Bot commented Jul 25, 2026

Copy link
Copy Markdown

📊 PR Size: size/L

  • Lines changed: 261
  • Additions: +256
  • Deletions: -5
  • Files changed: 3

@google-cla

google-cla Bot commented Jul 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.

@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 new script remove-npm-dist-tag.js (along with unit tests) to remove npm dist-tags with a retry mechanism, resolving issues where npm/Wombat acknowledges a publish before dist-tags are queryable. The release action is updated to use this script. The review feedback correctly points out that using execFileSync('sleep', ...) is platform-dependent and will fail on Windows. The reviewer suggests using Atomics.wait for a cross-platform synchronous sleep, and provides detailed suggestions to update the unit tests to mock and assert on Atomics.wait instead of the external sleep command.

Comment on lines +23 to +26
function sleep(ms) {
// Sync sleep so this helper stays callable from CI shell steps without top-level await.
execFileSync('sleep', [String(ms / 1000)], { stdio: 'ignore' });
}

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

Using execFileSync('sleep', ...) is platform-dependent and will fail on Windows environments (such as local developer machines or Windows-based CI runners) because the sleep command is not natively available. Spawning a child process just to pause execution also introduces unnecessary overhead.

Instead, use Atomics.wait to perform a synchronous, cross-platform sleep without spawning any external processes.

Suggested change
function sleep(ms) {
// Sync sleep so this helper stays callable from CI shell steps without top-level await.
execFileSync('sleep', [String(ms / 1000)], { stdio: 'ignore' });
}
function sleep(ms) {
// Sync sleep so this helper stays callable from CI shell steps without top-level await.
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
}

Comment on lines +16 to +19
describe('removeNpmDistTag', () => {
afterEach(() => {
vi.clearAllMocks();
});

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

To support the cross-platform Atomics.wait sleep implementation, mock Atomics.wait in a beforeEach block so that tests run instantly without actually sleeping.

describe('removeNpmDistTag', () => {
  beforeEach(() => {
    vi.spyOn(Atomics, 'wait').mockImplementation(() => 'ok');
  });

  afterEach(() => {
    vi.clearAllMocks();
    vi.restoreAllMocks();
  });

Comment on lines +43 to +74
execFileSync
.mockImplementationOnce(() => {
throw notReady;
})
.mockImplementationOnce(() => '') // sleep
.mockImplementationOnce(() => ''); // successful rm

removeNpmDistTag({
packageName: '@google/gemini-cli-core',
tag: 'staging-tmp',
maxAttempts: 3,
retryDelayMs: 1000,
});

expect(execFileSync).toHaveBeenNthCalledWith(
1,
'npm',
['dist-tag', 'rm', '@google/gemini-cli-core', 'staging-tmp'],
expect.any(Object),
);
expect(execFileSync).toHaveBeenNthCalledWith(
2,
'sleep',
['1'],
expect.any(Object),
);
expect(execFileSync).toHaveBeenNthCalledWith(
3,
'npm',
['dist-tag', 'rm', '@google/gemini-cli-core', 'staging-tmp'],
expect.any(Object),
);

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

Update the test assertions to reflect that execFileSync is no longer called for sleeping, and assert that Atomics.wait was called instead.

    execFileSync
      .mockImplementationOnce(() => {
        throw notReady;
      })
      .mockImplementationOnce(() => ''); // successful rm

    removeNpmDistTag({
      packageName: '@google/gemini-cli-core',
      tag: 'staging-tmp',
      maxAttempts: 3,
      retryDelayMs: 1000,
    });

    expect(execFileSync).toHaveBeenNthCalledWith(
      1,
      'npm',
      ['dist-tag', 'rm', '@google/gemini-cli-core', 'staging-tmp'],
      expect.any(Object),
    );
    expect(Atomics.wait).toHaveBeenCalledWith(
      expect.any(Int32Array),
      0,
      0,
      1000,
    );
    expect(execFileSync).toHaveBeenNthCalledWith(
      2,
      'npm',
      ['dist-tag', 'rm', '@google/gemini-cli-core', 'staging-tmp'],
      expect.any(Object),
    );

Comment on lines +103 to +120
execFileSync.mockImplementation((command) => {
if (command === 'sleep') {
return '';
}
throw notReady;
});

expect(() =>
removeNpmDistTag({
packageName: '@google/gemini-cli-core',
tag: 'staging-tmp',
maxAttempts: 2,
retryDelayMs: 1000,
}),
).toThrow(/Failed to remove dist-tag "staging-tmp"/);

// 2 rm attempts + 1 sleep between them
expect(execFileSync).toHaveBeenCalledTimes(3);

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

Update the exhausted retries test to assert on Atomics.wait instead of mocking the sleep command in execFileSync.

    execFileSync.mockImplementation(() => {
      throw notReady;
    });

    expect(() =>
      removeNpmDistTag({
        packageName: '@google/gemini-cli-core',
        tag: 'staging-tmp',
        maxAttempts: 2,
        retryDelayMs: 1000,
      }),
    ).toThrow(/Failed to remove dist-tag "staging-tmp"/);

    expect(execFileSync).toHaveBeenCalledTimes(2);
    expect(Atomics.wait).toHaveBeenCalledTimes(1);

@IshankDev

Copy link
Copy Markdown
Author

cla: yes

Large packages can be acknowledged by Wombat before dist-tags are
queryable, causing immediate `npm dist-tag rm staging-tmp` to fail the
nightly release. Retry until the tag propagates, using a cross-platform
Atomics.wait delay.

Fixes google-gemini#28533
@IshankDev
IshankDev force-pushed the contrib/28533 branch 2 times, most recently from 21072f0 to 19a3d6c Compare July 25, 2026 18:11
@gemini-cli gemini-cli Bot added priority/p1 Important and should be addressed in the near term. area/non-interactive Issues related to GitHub Actions, SDK, 3P Integrations, Shell Scripting, Command line automation labels Jul 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/non-interactive Issues related to GitHub Actions, SDK, 3P Integrations, Shell Scripting, Command line automation priority/p1 Important and should be addressed in the near term. size/l A large sized PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Nightly Release Failed for v0.54.0-nightly.20260725.g3818efbbf on 2026-07-25

1 participant