Skip to content

bump: add cooldown for npm and PyPI#21888

Draft
samford wants to merge 1 commit intomainfrom
bump-add-cooldown-for-npm-and-pypi
Draft

bump: add cooldown for npm and PyPI#21888
samford wants to merge 1 commit intomainfrom
bump-add-cooldown-for-npm-and-pypi

Conversation

@samford
Copy link
Copy Markdown
Member

@samford samford commented Apr 2, 2026


  • Have you followed the guidelines in our Contributing document?
  • Have you checked to ensure there aren't other open Pull Requests for the same change?
  • Have you added an explanation of what your changes do and why you'd like us to include them?
  • Have you written new tests (excluding integration tests) for your changes? Here's an example.
  • Have you successfully run brew lgtm (style, typechecking and tests) with your changes locally?

  • AI was used to generate or assist with generating this PR. Please specify below how you used AI to help you, and what steps you have taken to manually verify the changes.

This implements Mike's idea to add a cooldown to brew bump for npm and PyPI packages, in light of ongoing security incidents in npm in particular. The version_with_cooldown method checks upstream sources for version and release date information and identifies the highest version that was released before the cooldown interval.

This works based on very limited manual testing but there are some caveats:

  • The keys in the releases field in the PyPI JSON are sorted using string comparison (e.g., 1.2.30 is latest but 1.2.4, 1.2.5, etc. are after it), so this sorts using Version comparison before reverse iterating to find the highest suitable version. This works when the package uses a typical version scheme but I'm not sure if this will work as expected for all packages, so I'll have to do more testing.
  • npm packages can contain a variety of different version streams (e.g., dev, legacy, stable) with releases interleaved. As with the PyPI approach, this sorts using Version comparison before reverse iterating. Depending on how upstream handles versions, it may be possible for this approach to pick an unstable version, so this is something that I may need to rework.
  • npm packages with thousands of releases will have a JSON response that's several MB and this takes a notable amount of time to download and parse (e.g., wrangler is ~30 MB and takes ~30 seconds). This comes into play whenever livecheck surfaces a new version, so ideally we would cache the response etag and JSON data between bump runs to allow us to use If-None-Match in requests and avoid unnecessary downloads (like npm and pip). This is especially an issue for packages with an aggressive release cadence, where there may always be a new version available due to the cooldown interval. However, those may be good candidates to use throttle instead.

This still needs tests but it works as a proof of concept at this stage. I've opened this as a draft PR, so we can discuss and collaborate on an implementation. Feel free to build on this if it's a viable foundation. I'm not married to this implementation and I was primarily interested in identifying issues that we may face and ensuring that we don't need more information from livecheck. If there are better approaches, we should consider those.

This implements Mike's idea to add a cooldown to `brew bump` for
npm and PyPI packages, in light of ongoing security incidents in npm
in particular. The `version_with_cooldown` method checks upstream
sources for version and release date information and identifies the
highest version that was released before the cooldown interval.

This works based on very limited manual testing but there are some
caveats:

* The keys in the `releases` field in the PyPI JSON are sorted using
string comparison (e.g., 1.2.30 is latest but 1.2.4, 1.2.5, etc. are
after it), so this sorts using `Version` comparison before reverse
iterating to find the highest suitable version. This works when the
package uses a typical version scheme but I'm not sure if this will
work as expected for all packages, so I'll have to do more testing.
* npm packages can contain a variety of different version streams
(e.g., dev, legacy, stable) with releases interleaved. As with the
PyPI approach, this sorts using `Version` comparison before reverse
iterating. Depending on how upstream handles versions, it may be
possible for this approach to pick an unstable version, so this is
something that I may need to rework.
* npm packages with thousands of releases will have a JSON response
that's several MB and this takes a notable amount of time to download
and parse (e.g., `wrangler` is ~30 MB and takes ~30 seconds). This
comes into play whenever livecheck surfaces a new version, so ideally
we would cache the response etag and JSON data between bump runs to
allow us to use `If-None-Match` in requests and avoid unnecessary
downloads (like `npm` and `pip`). This is especially an issue for
packages with an aggressive release cadence, where there may always be
a new version available due to the cooldown interval. However, those
may be good candidates to use `throttle` instead.

This still needs tests but it works as a proof of concept at this
stage.
Copy link
Copy Markdown
Member

@MikeMcQuaid MikeMcQuaid left a comment

Choose a reason for hiding this comment

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

This approach looks very good and promising @samford, thanks for this and nice work on it. This is pretty much exactly what I had in mind, good job reading my 🧠. Let me know if I can provide any help or context here!

@cho-m cho-m mentioned this pull request Apr 4, 2026
6 tasks
@cho-m
Copy link
Copy Markdown
Member

cho-m commented Apr 5, 2026

Just noting that for PyPI formulae, it looks like the change to resource update does implicitly apply the cooldown to main formula, e.g. from last autobump run:

Unable to determine dependencies for "ty==0.0.29" because of a failure when running
`/home/linuxbrew/.linuxbrew/opt/python@3.14/libexec/bin/python -m pip install -q --disable-pip-version-check --dry-run --ignore-installed --uploaded-prior-to=2026-04-04T17:43:48Z --report=/dev/stdout ty==0.0.29`.
python3 -m pip install -q --disable-pip-version-check --dry-run --ignore-installed --uploaded-prior-to=2026-04-04T17:43:48Z --report=/dev/stdout ty==0.0.29
ERROR: Ignored the following yanked versions: 0.0.1
ERROR: Could not find a version that satisfies the requirement ty==0.0.29 (from versions: 0.0.0a1, 0.0.0a4, 0.0.0a5, 0.0.0a6, 0.0.0a7, 0.0.0a8, 0.0.1a1, 0.0.1a2, 0.0.1a3, 0.0.1a4, 0.0.1a5, 0.0.1a6, 0.0.1a7, 0.0.1a8, 0.0.1a9, 0.0.1a10, 0.0.1a11, 0.0.1a12, 0.0.1a13, 0.0.1a14, 0.0.1a15, 0.0.1a16, 0.0.1a17, 0.0.1a18, 0.0.1a19, 0.0.1a20, 0.0.1a21, 0.0.1a22, 0.0.1a23, 0.0.1a24, 0.0.1a25, 0.0.1a26, 0.0.1a27, 0.0.1a28, 0.0.1a29, 0.0.1a30, 0.0.1a31, 0.0.1a32, 0.0.1a33, 0.0.1a34, 0.0.1a35, 0.0.2, 0.0.3, 0.0.4, 0.0.5, 0.0.6, 0.0.7, 0.0.8, 0.0.9, 0.0.10, 0.0.11, 0.0.12, 0.0.13, 0.0.14, 0.0.15, 0.0.16, 0.0.17, 0.0.18, 0.0.19, 0.0.20, 0.0.21, 0.0.22, 0.0.23, 0.0.24, 0.0.25, 0.0.26, 0.0.27, 0.0.28)

Though still need to make brew bump aware of cooldown if multiple updates happen in less than 24 hour period and to get green autobump CI runs.


For npm, it would be nice to get something in earlier and improve it in follow ups. Right now we need to manually delay CI runs by a day:

Assuming complexity of re-implementing version resolution from npm is reasonable, then approach here should be good with some additional filters to deal with interleaving. Some ideas

If complexity grows too much, then may consider delegating to npm itself.

@cho-m
Copy link
Copy Markdown
Member

cho-m commented Apr 5, 2026

npm packages with thousands of releases will have a JSON response that's several MB and this takes a notable amount of time to download and parse (e.g., wrangler is ~30 MB and takes ~30 seconds)

Is the time due to some special parsing we are doing? EDIT: or perhaps our user-agent is throttled?

Just a naive decode/encode in hash seems quick:

time (brew ruby -e 'p JSON.parse(`curl -s --compressed https://registry.npmjs.org/wrangler`).to_json.size')
29254916
( brew ruby -e ; )  0.64s user 0.23s system 62% cpu 1.387 total

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants