Summary
The context under which a shared package's children are resolved (and its missing-peer report computed) is decided by whichever occurrence completes resolution first — an async race shaped by network/cache timing. This is the last source of resolution nondeterminism surfaced by the pacquet lockfile-parity work (#12266) and the common root of several user-visible flakes.
Mechanism
ctx.resolvedPkgsById[pkgId] is claimed by the first occurrence to finish (resolveDependencies.ts#L1631-L1635); only that occurrence runs resolveChildren, under its per-level preferredVersions overlay and alias chain.
ctx.missingPeersOfChildrenByPkgId[pkgId] records the subtree's missing-peer report once, computed under the winner's chain (#L1703-L1725); revisits await the shared promise.
- Which occurrence wins depends on packument-fetch completion order. The race is structurally biased toward the shallowest occurrence (its request starts RTTs earlier), but near-ties flip with cache state.
Observable symptoms
Why it isn't a quick fix
A correct deterministic winner needs claim takeover: when a deterministically-better occurrence arrives after a worse one already claimed, the children must be re-resolved under the better context. Two structural blockers:
- The claim's outputs are structure-shared across all occurrences — the
missingPeersOfChildren promise may already be consumed by importer-level hoist logic by the time the better occurrence arrives, and revisit occurrences realize their tree structure from the first walk's children.
- The code already notes the constraint: "There might be a better way to hoist peer dependencies during resolution but it would probably require a big rewrite of the resolution algorithm" (#L1705-L1707).
Proposed direction
Define the winner as the smallest (depth, importer order, parent path) occurrence — matching the de-facto shallowest-wins outcome so lockfile churn from the change is minimal — with redo machinery for the rare out-of-order arrival (a deep occurrence completing before a shallow one). pacquet would adopt the identical rule (its sequential rounds make the redo simpler); pnpm's half is the substantial part: re-running resolveChildren under the new context, replacing the per-pkgId record, and keeping already-realized occurrences consistent.
Until then, pacquet's claimer (first importer in sorted order, then walk order — deterministic) and pnpm's (race) can disagree on context-sensitive picks; on the pnpm monorepo that is currently a single lockfile line.
Surfaced while working #12266; the deterministic barrier alignment for hoist rounds landed in #12357.
Written by an agent (Claude Code, claude-fable-5).
Summary
The context under which a shared package's children are resolved (and its missing-peer report computed) is decided by whichever occurrence completes resolution first — an async race shaped by network/cache timing. This is the last source of resolution nondeterminism surfaced by the pacquet lockfile-parity work (#12266) and the common root of several user-visible flakes.
Mechanism
ctx.resolvedPkgsById[pkgId]is claimed by the first occurrence to finish (resolveDependencies.ts#L1631-L1635); only that occurrence runsresolveChildren, under its per-levelpreferredVersionsoverlay and alias chain.ctx.missingPeersOfChildrenByPkgId[pkgId]records the subtree's missing-peer report once, computed under the winner's chain (#L1703-L1725); revisits await the shared promise.Observable symptoms
pnpm dedupe --checkCI failures on unchanged commits (the class behind fix: prevent a pinned locked peer provider from leaking to sibling nodes #12320).@types/ssri@7.1.5's@types/node: '*'child resolves to25.9.3or22.19.21depending on whether the importer-level occurrence (no@types/nodein its level) or the root importer's deeper occurrence (level carries22.19.21) claims the children context.Why it isn't a quick fix
A correct deterministic winner needs claim takeover: when a deterministically-better occurrence arrives after a worse one already claimed, the children must be re-resolved under the better context. Two structural blockers:
missingPeersOfChildrenpromise may already be consumed by importer-level hoist logic by the time the better occurrence arrives, and revisit occurrences realize their tree structure from the first walk's children.Proposed direction
Define the winner as the smallest
(depth, importer order, parent path)occurrence — matching the de-facto shallowest-wins outcome so lockfile churn from the change is minimal — with redo machinery for the rare out-of-order arrival (a deep occurrence completing before a shallow one). pacquet would adopt the identical rule (its sequential rounds make the redo simpler); pnpm's half is the substantial part: re-runningresolveChildrenunder the new context, replacing the per-pkgId record, and keeping already-realized occurrences consistent.Until then, pacquet's claimer (first importer in sorted order, then walk order — deterministic) and pnpm's (race) can disagree on context-sensitive picks; on the pnpm monorepo that is currently a single lockfile line.
Surfaced while working #12266; the deterministic barrier alignment for hoist rounds landed in #12357.
Written by an agent (Claude Code, claude-fable-5).