Skip to content

pnpm up -r skips transitive updates when mixed with direct dependency selectors #12103

Description

@aqeelat

Summary

pnpm up -r <transitive-pattern> <direct-dependency> can skip recursive transitive updates for the transitive pattern.

In a real workspace, pnpm up -r "@babel/core" updates transitive @babel/core to 7.29.7, but pnpm up -r "@babel/core" uuid does not when uuid is a direct dependency selector.

Minimal Reproduction

Verified with pnpm v11.5.0 in a 27-project Angular monorepo (~/fathom/frontend/fathom-frontend-bench1, lockfileVersion 9.0).

git checkout HEAD -- pnpm-lock.yaml
pnpm up -r "@babel/core"
rg '@babel/core@' pnpm-lock.yaml
# => '@babel/core@7.29.7' is present

git checkout HEAD -- pnpm-lock.yaml
pnpm up -r "@babel/core" uuid
rg '@babel/core@' pnpm-lock.yaml
# => '@babel/core@7.29.7' is missing

The original long command failed for the same reason because it mixed transitive patterns with uuid, which is a direct dependency in the root manifest:

pnpm up -r fast-uri "@babel/*" brace-expansion webpack-dev-server hono \
  "@hono/*" fast-xml-builder fast-xml-parser ip-address ws uuid qs

Removing uuid from that command makes the @babel/* recursive transitive update work.

Root Cause

The resolver assigns update depth for existing dependencies in installing/deps-resolver/src/toResolveImporter.ts.

noDependencySelectors is computed globally in installing/deps-resolver/src/index.ts:

noDependencySelectors: importers.every(({ wantedDependencies }) => wantedDependencies.length === 0)

When any direct selector exists (for example uuid), noDependencySelectors becomes false. Then existing transitive dependencies are assigned updateDepth: -1 even when project.updateMatching exists.

Debugging showed this for @babel/core in the mixed command:

updateMatchingResult: true
updateDepth: -1
updateShouldContinue: false
updateRequested: false

So the matcher correctly identifies @babel/core, but the depth check prevents the update. Since updateRequested=false, resolveDependency rewrites ranges like ^7.23.2 to the current exact lockfile version, keeping the old resolved version.

Expected Behavior

Mixing direct dependency selectors and transitive update patterns should not disable recursive updates for matching transitive packages.

pnpm up -r "@babel/core" uuid should update transitive @babel/core just like pnpm up -r "@babel/core" does.

Actual Behavior

The direct selector causes existing transitive dependencies to get updateDepth: -1, so matching transitive packages are not updated.

Fix Direction

When project.updateMatching exists, existing dependencies should keep defaultUpdateDepth. updateMatching already filters package-by-package, so applying the depth does not mean every transitive package is updated.

Related


Written by an agent (opencode, gpt-5.5).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    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