You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Make the lockfile freshness check use the resolver's local-dependency rules #255
A frozen install or freshness check judges local, injected, and directory dependencies the way resolution would.
Evidence
pnpm/crates/package-manager/src/install/lockfile_freshness has 24 fixes on 530 lines in the 90 days to 2026-11-03. The fixes copy one resolver rule each:
One decision with two deciders. lockfile_freshness keeps its own copy of the resolver's rules for directory and injected dependencies, and each fix copies one more rule.
Plan
Move the resolver's judgment of local, injected, and directory dependencies into a function in the resolving crates that both the resolver and the freshness check call.
Replace the copies in install/lockfile_freshness with calls to it.
Add a differential test: for each freshness fixture, run resolution and the freshness check and assert they agree on whether the lockfile is current.
Written by an agent (Claude Code, claude-opus-5-5).
This maps the resolver rules that lockfile_freshness/directory_deps/ and directory_manifests.rs copy, so the remaining extraction can be planned rule by rule.
Already shared: the child-spec walk (local_directory_child_specs), peer shadowing, PeerSet, resolve_from_catalog, the reuse gate (locked_child_admits), the manifest hook chain, VersionsOverrider, and publishes_from_directory.
1. Children of a path-declared directory skip catalogs
shared (is_local_filesystem_specifier) in pnpm/pnpm#17722. Fixed a bug in both CLIs: a catalog: child of an injected ../lib (and in v11 also file:../lib) passed the check, on frozen installs too
12. Which manifest the hooks run on: the check reads the hooked manifests the install captured. The resolver runs the async hooks itself, so there is no function to share.
15. Peer scope: the check has no resolved tree and rebuilds the scope from lockfile keys.
Differential test: the freshness matrices in both CLIs (the Rust matrix and pnpm11/pnpm/test/monorepo/injectedFreshnessMatrix.ts) assert, for each layout, injection form, edit, and filter, that a frozen install fails exactly when a non-frozen install changes the lockfile.
Left over:#274. With an injected package that depends on a sibling through ../child or link:../child, a frozen install rejects the lockfile pnpm install just wrote, and the two CLIs record that link differently.
Closing as completed.
Written by an agent (Claude Code, claude-opus-5-5).
Activity
fix: treat a link to a directory without a manifest as stale
fix: read a publish directory link's version from its project manifest
fix: keep a project's manifest when another project publishes from it…
fix: judge a workspace: range in the lockfile checks the way the reso…
zkochan commentedon Nov 5, 2026
Rule map and progress
This maps the resolver rules that
lockfile_freshness/directory_deps/anddirectory_manifests.rscopy, so the remaining extraction can be planned rule by rule.Already shared: the child-spec walk (
local_directory_child_specs), peer shadowing,PeerSet,resolve_from_catalog, the reuse gate (locked_child_admits), the manifest hook chain,VersionsOverrider, andpublishes_from_directory.Copied:
file:directory skip catalogschildren.rs,directory_manifests.rs(!declared_as_file)resolves_children_through_catalogsChildCatalogs.package_dirdeclaring_manifest_dirpeers.rscheck_recorded_peer_specs_matchextract_peer_dependenciesfile:exception and the taken-peers filterchildren.rscheck_snapshot_keys_are_childrendeclared_peer_dependenciesfile:/link:path equalityspec.rsresolve_local_spec_pathpnpm_local_spec::normalize_specifier, local resolverresolve_pathfile:is_local_tarball_pathis_tarball_filenameworkspace:grammarspec.rsparse_workspace_range,is_workspace_pathWorkspaceSpec::parse,resolve_from_workspaceworkspace:range matchingspec.rs,manifest_agreement.rsresolve_workspace_rangeinjected_manifest.rsresolve_workspace_package_dirlinkDirectory: false, no disagreement foundinjected_manifest.rsunhooked_manifestresolve_spec,resolve_from_local_packagefile:: project manifests before diskpackage.rs,directory_manifests.rsapply_manifest_hooksimporter_deps.rsdependency_is_injectedresolve_dependency_tree.rsdependency_is_injectedscope.rsParentPkgAliasesRules 5, 14, 16 and 17 depend on the lockfile's shape and have no resolver counterpart. Rule 16 goes away with #228, planned for pnpm v13.
Written by an agent (Claude Code, claude-opus-5-5).
test(cli): re-spell the injected package's local dependencies instead…
test(cli): give the injected package its own local dependencies in th…
fix: read a local dependency path the way the resolver does in the lo…
refactor: allow a directory snapshot's peers through the resolver's p…
fix: classify a bare-path injected dependency the way the resolver does
refactor: locate a directory package through the resolver's function
refactor: decide whether a workspace: spec names a path in one place
fix: leave the catalog: children of a path-declared package unresolve…
fix: share the remaining local-dependency rules between the resolver …
zkochan commentedon Nov 5, 2026
Final status
The plan is done. pnpm/pnpm#17706, pnpm/pnpm#17720, and pnpm/pnpm#17722 move every rule in the map that can be shared into the resolving crates. pnpm/pnpm#17716 extends the freshness matrix.
is_local_filesystem_specifier) in pnpm/pnpm#17722. Fixed a bug in both CLIs: acatalog:child of an injected../lib(and in v11 alsofile:../lib) passed the check, on frozen installs toodirectory_package_dir) in pnpm/pnpm#17722declared_peer_dependencies) in pnpm/pnpm#17722file:/link:path equalityworkspace:path formsis_workspace_path_specifier) in pnpm/pnpm#17722workspace:range matchingdependency_is_injected,dependency_meta_is_injected) in pnpm/pnpm#17722Stay separate:
catalog:peer, and the check also accepts the resolved range (--frozen-lockfile fails with "local dependency is outdated" when an injected workspace package has a catalog: peer dependency pnpm#16557). An exact comparison would reject lockfiles that recorded the resolved form. The peer set itself is already the sharedPeerSet.publishes_from_directory.Differential test: the freshness matrices in both CLIs (the Rust matrix and
pnpm11/pnpm/test/monorepo/injectedFreshnessMatrix.ts) assert, for each layout, injection form, edit, and filter, that a frozen install fails exactly when a non-frozen install changes the lockfile.Left over: #274. With an injected package that depends on a sibling through
../childorlink:../child, a frozen install rejects the lockfilepnpm installjust wrote, and the two CLIs record that link differently.Closing as completed.
Written by an agent (Claude Code, claude-opus-5-5).