Skip to content

pnpm outdated recommends versions inside the minimumReleaseAge cooldown window #11698

@timhaines

Description

@timhaines

pnpm outdated recommends versions inside the minimumReleaseAge window

Summary

With minimumReleaseAge configured, pnpm outdated reports an "available
upgrade" to a version that is still inside the cooldown window. The install
path honours the cooldown; outdated does not. Workflows that use
pnpm outdated to decide what to bump are therefore steered straight into the
versions the cooldown exists to hold back.

Repro

  • pnpm-workspace.yaml: minimumReleaseAge: 10080 (no explicit
    minimumReleaseAgeStrict — correctly relying on the strict-by-default that
    pnpm auto-enables since 11.0.4 when minimumReleaseAge is set).
  • A dependency whose latest dist-tag points at a build published within the
    window. Constantly true for @typescript/native-preview, whose latest is
    always a fresh dated -dev prerelease.
  • Run pnpm -r outdated.

Expected: package not reported — there is no mature version newer than
what is installed. Actual: the immature version is reported as an upgrade.

Normal deps look correct because their latest is a stable release with many
mature versions; the bug only shows when no version satisfies the cooldown
for the resolved spec — which is the steady state for prerelease-only packages.

Root cause

outdated resolves the latest dist-tag through
deps/inspection/outdated/src/createManifestGetter.ts:

strictPublishedByCheck: Boolean(opts.minimumReleaseAge) && opts.minimumReleaseAgeStrict === true,

When strictPublishedByCheck is off, the npm resolver's non-strict fallback
applies (resolving/npm-resolver/src/pickPackage.ts,
pickRespectingMinReleaseAge): if no version satisfies the maturity cutoff it
re-picks while ignoring publishedBy, returning the immature latest.
That fallback exists so an install doesn't hard-fail when every candidate is
fresh — but for a read-only outdated report it turns "no mature upgrade"
into "here is an immature upgrade".

This is reachable two ways, both fixed by the same change: (1) users who
deliberately set minimumReleaseAgeStrict: false (legitimate: don't hard-fail
CI, but also don't recommend immature upgrades); (2) the auto-enabled strict
default not reliably reaching the outdated command's resolver.
minimumReleaseAgeStrict is an install-resilience knob; a report has no
install to rescue, so coupling outdated to it is the design flaw.

Suggested fix

outdated should respect the cooldown unconditionally when minimumReleaseAge
is active:

strictPublishedByCheck: Boolean(opts.minimumReleaseAge),

getManifest already maps NO_MATURE_MATCHING_VERSION /
NO_MATCHING_VERSION to "nothing to report", and createManifestGetter is
used only by outdated / outdated --recursive, so install behaviour is
unaffected. Verified locally: a regression test (load has-outdated-deps, set
an all-immature cutoff, assert is-negative@2.1.0 is not offered) goes red →
green with this one-line change; the no-policy baseline keeps passing.


Written by an agent (Claude Code, claude-opus-4-7).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions