Skip to content

[workers-utils] Stop the update check from recommending deprecated versions - #15617

Open
jpatel3 wants to merge 2 commits into
cloudflare:mainfrom
jpatel3:fix/update-check-skip-deprecated-versions
Open

[workers-utils] Stop the update check from recommending deprecated versions#15617
jpatel3 wants to merge 2 commits into
cloudflare:mainfrom
jpatel3:fix/update-check-skip-deprecated-versions

Conversation

@jpatel3

@jpatel3 jpatel3 commented Sep 12, 2026

Copy link
Copy Markdown

Fixes #9154.

When a Wrangler release is deprecated on npm (e.g. 4.14.2 was deprecated for a wrangler dev regression), users on an older version still saw (update available 4.14.2) in the banner, nudging them towards a known-bad release.

The update-check package we used fetched the full abbreviated packument, which includes the per-version deprecated field, but only kept the dist-tags version. Fixing this on top of the library would have required a second registry request whenever an update was found, and the banner only waits ~100ms for the (normally cached) result, so that would have hidden the notice for everyone who was behind.

Instead fetchLatestNpmVersion in @cloudflare/workers-utils now queries the registry itself (via undici, which the package already depends on) and the update-check dependency is removed:

  • If the version behind the dist tag (latest, or beta for 0.0.0-* builds) is not deprecated, behaviour is unchanged.
  • If it is deprecated, the newest non-deprecated stable release below it is recommended instead. Pre-releases and stable versions newer than latest (e.g. a next tag) are never picked. If nothing suitable exists, the check reports up-to-date.
  • The on-disk cache keeps the same location (<tmpdir>/update-check/<name>-<tag>.json), file format and one-hour TTL, so caches written by the previous implementation continue to be honoured. Cache write failures no longer turn a successful check into a failure.
  • The 3s overall timeout is kept, and the registry request itself is now aborted on that timeout instead of being left dangling.

One deliberate simplification to flag for reviewers: update-check resolved the registry URL from .npmrc and retried 4xx responses with an auth token. This implementation always uses https://registry.npmjs.org/, since both packages that use this check (wrangler and @cloudflare/vite-plugin) are only published there. Users whose network blocks the public registry will get the existing silent failed result. Happy to add npm_config_registry support if you'd prefer to keep that behaviour.

wrangler's own tests are unaffected because they mock updateCheck at the wrangler layer.


  • Tests
    • Tests included/updated
    • Automated tests not possible - manual testing has been completed as follows:
    • Additional testing not necessary because:
  • Public documentation
    • Cloudflare docs PR(s):
    • Documentation not necessary because: this only changes which version the existing "update available" notice suggests; there are no new commands, flags or configuration.

Devin Review

…rsions

The "update available" notice always pointed at whichever version was
tagged `latest` on npm, even after that version had been deprecated for
shipping a bug. The `update-check` package fetched the full packument,
which carries the per-version `deprecated` field, but discarded it.

Query the npm registry directly instead: if the tagged version has been
deprecated, recommend the newest non-deprecated stable release below it,
or nothing at all. The on-disk cache location, file format and one-hour
refresh interval are unchanged so existing caches keep working.

Fixes cloudflare#9154
@changeset-bot

changeset-bot Bot commented Sep 12, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: acd4bc1

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 10 packages
Name Type
wrangler Patch
@cloudflare/vite-plugin Patch
@cloudflare/workers-utils Patch
@cloudflare/vitest-plugin Patch
@cloudflare/autoconfig Patch
@cloudflare/cli-shared-helpers Patch
@cloudflare/containers-shared Patch
@cloudflare/deploy-helpers Patch
@cloudflare/remote-bindings Patch
@cloudflare/workers-auth Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-project-automation github-project-automation Bot moved this to Untriaged in workers-sdk Sep 12, 2026
@workers-devprod
workers-devprod requested review from a team and dario-piotrowicz and removed request for a team September 12, 2026 00:49
@workers-devprod

Copy link
Copy Markdown
Contributor

Codeowners approval required for this PR:

  • @cloudflare/wrangler
Show detailed file reviewers
  • .changeset/quiet-deprecated-update-check.md: [@cloudflare/wrangler]
  • packages/workers-utils/package.json: [@cloudflare/wrangler]
  • packages/workers-utils/src/update-check.ts: [@cloudflare/wrangler]
  • packages/workers-utils/tests/update-check.test.ts: [@cloudflare/wrangler]
  • pnpm-lock.yaml: [@cloudflare/wrangler]

@pkg-pr-new

pkg-pr-new Bot commented Sep 12, 2026

Copy link
Copy Markdown
@cloudflare/autoconfig

npm i https://pkg.pr.new/@cloudflare/autoconfig@15617

@cloudflare/build-output-utils

npm i https://pkg.pr.new/@cloudflare/build-output-utils@15617

@cloudflare/codemods

npm i https://pkg.pr.new/@cloudflare/codemods@15617

@cloudflare/config

npm i https://pkg.pr.new/@cloudflare/config@15617

@cloudflare/containers-shared

npm i https://pkg.pr.new/@cloudflare/containers-shared@15617

create-cloudflare

npm i https://pkg.pr.new/create-cloudflare@15617

@cloudflare/deploy-helpers

npm i https://pkg.pr.new/@cloudflare/deploy-helpers@15617

@cloudflare/kv-asset-handler

npm i https://pkg.pr.new/@cloudflare/kv-asset-handler@15617

miniflare

npm i https://pkg.pr.new/miniflare@15617

@cloudflare/pages-functions

npm i https://pkg.pr.new/@cloudflare/pages-functions@15617

@cloudflare/pages-shared

npm i https://pkg.pr.new/@cloudflare/pages-shared@15617

@cloudflare/unenv-preset

npm i https://pkg.pr.new/@cloudflare/unenv-preset@15617

@cloudflare/vite-plugin

npm i https://pkg.pr.new/@cloudflare/vite-plugin@15617

@cloudflare/vitest-plugin

npm i https://pkg.pr.new/@cloudflare/vitest-plugin@15617

@cloudflare/workers-auth

npm i https://pkg.pr.new/@cloudflare/workers-auth@15617

@cloudflare/workers-editor-shared

npm i https://pkg.pr.new/@cloudflare/workers-editor-shared@15617

@cloudflare/workers-utils

npm i https://pkg.pr.new/@cloudflare/workers-utils@15617

wrangler

npm i https://pkg.pr.new/wrangler@15617

commit: acd4bc1

@devin-ai-integration devin-ai-integration 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.

Devin Review found 1 potential issue.

1 flag not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)

Devin Review

Comment on lines +66 to 70
latest = await Promise.race([
getLatestVersion(name, distTag),
timersPromises.setTimeout(UPDATE_CHECK_TIMEOUT_MS, TIMED_OUT, {
ref: false,
}),

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.

🟡 Timed-out checks continue in background

After a slow cache read, Promise.race returns failure while getLatestVersion continues and starts the registry request. Timed-out checks still generate registry traffic and can delay process exit.

Learn more

The timeout promise only stops waiting for getLatestVersion; it does not cancel that operation. A cache read lasting beyond three seconds therefore produces a failed result, but the pending operation resumes when the read completes and calls fetchPackument. That request gets a fresh three-second timeout because its AbortSignal.timeout() starts only when the request begins. The operation can then also write the cache after the public function has returned.

Example: A cache read takes 3.1 seconds. The caller receives failed at 3 seconds. At 3.1 seconds, getLatestVersion starts an npm request that can run until 6.1 seconds, despite the stated three-second overall budget.

Recommended fix: Create one abort signal when fetchLatestNpmVersion starts and pass it through getLatestVersion, readCache, fetchPackument, and cache writes. Use that signal for supported filesystem calls and fetch, and avoid starting later stages once it is aborted.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

* - `{ status: "up-to-date" }` if the installed version is already the latest
* - `{ status: "failed" }` if the check could not be completed (network error, timeout, etc.)
*/
export async function fetchLatestNpmVersion(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

mh... I am wondering if we should rename this function, to something like fetchLatestValidNpmVersion or fetchLatestActiveNpmVersion 🤔

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I'd lean towards keeping it: the export already existed before this PR and is used by wrangler and the vite plugin, and the docblock covers the deprecation behaviour. Not against renaming if you feel strongly about it though.

/**
* Pick the version to recommend from a packument.
*
* @returns The version behind `distTag`, unless it has been deprecated, in

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

could you also add @params? 🙏

@jpatel3 jpatel3 Sep 14, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Added in acd4bc1.


async function fetchPackument(name: string): Promise<AbbreviatedPackument> {
const packageUrl = new URL(
encodeURIComponent(name).replace(/^%40/, "@"),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

what is replace for?

@jpatel3 jpatel3 Sep 14, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

It mirrored how npm itself encodes scoped names in registry URLs (@scope%2Fname), but the registry accepts the fully encoded form too, so it wasn't needed. Dropped it in acd4bc1.

@dario-piotrowicz

Copy link
Copy Markdown
Member

Btw, sorry, besides my comments above this looks good to me, thanks for this fix @jpatel3 🙏 🫶

@jpatel3
jpatel3 force-pushed the fix/update-check-skip-deprecated-versions branch from 4bba64e to acd4bc1 Compare September 14, 2026 13:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Untriaged

Development

Successfully merging this pull request may close these issues.

Wrangler upgrade check does not respect deprecated packages

3 participants