Skip to content

fix(npm-resolver): minimumReleaseAge handling for cached abbreviated metadata #11622

Merged
zkochan merged 6 commits into
pnpm:mainfrom
mandarini:fix/npm-resolver-missing-time-version-spec-cache
May 14, 2026
Merged

fix(npm-resolver): minimumReleaseAge handling for cached abbreviated metadata #11622
zkochan merged 6 commits into
pnpm:mainfrom
mandarini:fix/npm-resolver-missing-time-version-spec-cache

Conversation

@mandarini

@mandarini mandarini commented May 13, 2026

Copy link
Copy Markdown
Contributor

Summary

Two related bugs in how pickPackage handles minimumReleaseAge when local metadata is the abbreviated form (no per-version time). Both are contained to resolving/npm-resolver/src/pickPackage.ts.

Bug 1 — ERR_PNPM_MISSING_TIME rethrown from the version-spec cache path

The version-spec cache fast path (spec.type === 'version') wraps pickMatchingVersionFast in a try/catch that rethrows under strictPublishedByCheck. The adjacent mtime-gated cache block filters out ERR_PNPM_MISSING_TIME before rethrowing; the version-spec block does not. Both blocks share the same intent ("try cached fast path, fall through to the registry fetch on failure"), so this is a missing filter, not a design choice.

Surfaced as a hard error in workspaces with minimumReleaseAge set, because v11.0.4 (#1143642a8f29f41 on the 11.0.x branch, e3ccf6b134 on main) made minimumReleaseAgeStrict: true the default when the user explicitly sets minimumReleaseAge.

Fix: add !isMissingTimeError(err) to the catch filter, matching the adjacent block.

Bug 2 — silent bypass via 304 Not Modified

After Bug 1, the version-spec catch swallows MISSING_TIME and falls through to the network fetch. If the registry returns 304 Not Modified (etag still valid), pnpm reuses the cached abbreviated metadata and never upgrades to full. The downstream pickMatchingVersionFinal then takes its warn-and-skip path — emitting a misleading "metadata is missing the time field" warning and silently bypassing the maturity check.

This is the same family as #11619: registry has time available in the full format, but pnpm never asks for it from cached paths.

Fix: at the 304 path, if the cached meta is abbreviated and minimumReleaseAge would need time, re-fetch with fullMetadata: true and persist the upgraded meta to disk so subsequent installs don't repeat the fetch. Factored into a helper (maybeUpgradeAbbreviatedMetaForReleaseAge) that mirrors the upgrade condition already present in the post-fetch path.


Reproduction

  1. pnpm >= 11.0.4.
  2. pnpm-workspace.yaml:
    minimumReleaseAge: 10080  # 7 days
  3. package.json pinning an exact version whose abbreviated metadata is cached locally (e.g. "vitest": "4.1.4" after a prior install).
  4. pnpm install[ERR_PNPM_MISSING_TIME] The metadata of vitest is missing the "time" field, even though npm view vitest time shows the registry has it.

Workaround that confirms the strict path: minimumReleaseAgeStrict: false.


Tests

Two regression tests added to resolving/npm-resolver/test/publishedBy.test.ts:

  1. strictPublishedByCheck=true does not rethrow ERR_PNPM_MISSING_TIME from the version-spec cache path — covers Bug 1.
  2. upgrades cached abbreviated metadata to full when 304 Not Modified and publishedBy is set — covers Bug 2, including the disk-persistence assertion.

Both fail before their respective fix and pass after.

Related

Summary by CodeRabbit

  • Bug Fixes

    • Fix minimumReleaseAge filtering for cached abbreviated package metadata: pnpm now upgrades abbreviated cache to full metadata when needed (including on conditional 304 responses), selects the correct versions, and persists upgrades to disk to avoid repeated refetches and incorrect maturity handling. Fast-path resolution for exact versions no longer fails spuriously when time fields are missing.
  • Tests

    • Added regression tests covering cached-abbreviated behavior, 304 handling, and strict published-by checks.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 13, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR upgrades cached abbreviated npm metadata to full metadata when maturity checks (publishedBy/minimumReleaseAge) require per-version time, suppresses ERR_PNPM_MISSING_TIME in the version-spec cache fast path to allow network fallback, and persists upgraded metadata after 304 responses.

Changes

Abbreviated Metadata Upgrade for Maturity Checks

Layer / File(s) Summary
Abbreviated metadata upgrade helper
resolving/npm-resolver/src/pickPackage.ts
New maybeUpgradeAbbreviatedMetaForReleaseAge function evaluates whether cached abbreviated metadata requires full-metadata refetch by comparing the package modified date against the maturity cutoff when publishedBy checks are active, returning either the original or upgraded meta with fetch result for optional persistence; also adds shouldRethrowFromFastPathCache and persistUpgradedMeta.
In-memory cache hit upgrade
resolving/npm-resolver/src/pickPackage.ts
On in-memory meta cache hits, the code calls the upgrade helper for abbreviated metadata when maturity filtering applies; if upgraded, it updates ctx.metaCache and (unless dryRun) persists upgraded full metadata before selecting the matching version.
Disk-cached meta upgrade (offline/prefer-offline/low-version)
resolving/npm-resolver/src/pickPackage.ts
When loading metadata from disk, the same abbreviated→full upgrade is attempted and the loaded meta is replaced with the upgraded version, updating the in-memory cache when applicable and persisting to disk unless dryRun.
Error handling for cached version-spec fast path
resolving/npm-resolver/src/pickPackage.ts
In the cached spec.type === 'version' fast path, caught errors are typed as unknown and, when ctx.strictPublishedByCheck is set, ERR_PNPM_MISSING_TIME is suppressed to allow fallback to network fetching.
304 Not Modified upgrade and persistence
resolving/npm-resolver/src/pickPackage.ts
Integrates the upgrade helper into the 304 Not Modified flow: after loading cached metadata, conditionally refetches full metadata when needed and persists the upgraded result to disk (applying filterMetadata/clearing) to prevent future abbreviated-metadata misses.
Regression tests and changeset documentation
resolving/npm-resolver/test/publishedBy.test.ts, .changeset/fix-minimum-release-age-cached-abbreviated-metadata.md
Three Jest regression tests validate fast-path error suppression and 304 upgrade/persistence behavior; changeset documents the fix for @pnpm/resolving.npm-resolver and pnpm packages.

Sequence Diagram(s):

sequenceDiagram
  participant PickPackage
  participant MetaCache
  participant DiskCache
  participant Registry
  PickPackage->>MetaCache: read cached meta (abbreviated?)
  alt abbreviated meta and publishedBy/minimumReleaseAge active
    MetaCache->>PickPackage: abbreviated meta missing per-version time
    PickPackage->>Registry: fetch full metadata
    Registry->>PickPackage: return full metadata with time
    PickPackage->>MetaCache: update in-memory cache with full meta
    alt 304 path persistence
      PickPackage->>DiskCache: persist upgraded full meta to disk
    end
  else use cached meta
    MetaCache->>PickPackage: use cached meta
  end
  PickPackage->>PickPackage: pickMatchingVersionFinal using chosen meta
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Suggested reviewers

  • zkochan

Poem

🐰 I nudge the cache and sniff the date,
If times are missing, I fetch what's great.
I patch the disk so next time it's fine,
Hop, fetch, and store — the registry's mine! 🥕

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely describes the main fix: addressing minimumReleaseAge handling for cached abbreviated metadata in npm-resolver.
Linked Issues check ✅ Passed The PR directly addresses issue #11619 by fixing ERR_PNPM_MISSING_TIME handling and implementing metadata upgrade logic for 304 Not Modified responses.
Out of Scope Changes check ✅ Passed All changes are scoped to npm-resolver's pickPackage.ts logic, publishedBy tests, and a corresponding changeset entry—all directly related to the minimumReleaseAge and metadata handling fix.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Comment @coderabbitai help to get the list of available commands and usage tips.

@mandarini mandarini marked this pull request as ready for review May 13, 2026 18:58
@mandarini mandarini requested a review from zkochan as a code owner May 13, 2026 18:58
Copilot AI review requested due to automatic review settings May 13, 2026 18:58

Copilot AI 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.

Pull request overview

Fixes a bug in pickPackage where the spec.type === 'version' cache fast path rethrew ERR_PNPM_MISSING_TIME under strictPublishedByCheck instead of falling through to the registry fetch. The fix makes this catch block consistent with the adjacent mtime-gated cache block, which already filters out missing-time errors. This surfaced after #11436 made minimumReleaseAgeStrict default to true when users explicitly set minimumReleaseAge.

Changes:

  • Added isMissingTimeError filter to the version-spec cache catch block in pickPackage.ts.
  • Added a regression test in publishedBy.test.ts that strips time from cached abbreviated metadata and asserts an exact-version resolve under strictPublishedByCheck: true falls through to the registry fetch.
  • Added a patch-level changeset describing the fix.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
resolving/npm-resolver/src/pickPackage.ts Filters ERR_PNPM_MISSING_TIME in the version-spec cache catch block so it falls through to the network fetch, matching the sibling mtime-gated block.
resolving/npm-resolver/test/publishedBy.test.ts Adds a regression test exercising the version-spec cache path with stripped time and strictPublishedByCheck: true.
.changeset/fix-missing-time-rethrown-from-version-spec-cache.md Patch changeset for @pnpm/resolving.npm-resolver and pnpm.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@mandarini

Copy link
Copy Markdown
Contributor Author

@zkochan I think this is indeed a bug with how the time error is being treated.

Screenshot 2026-05-14 at 9 09 47 AM

After my change, it turns the error in a warning, but this is still wrong, because vitest has the timefield (as lodash has as mentioned in issue #11619
Screenshot 2026-05-14 at 9 14 00 AM

I'm trying to track down the culprit!

@mandarini mandarini changed the title fix(npm-resolver): dont rethrow ERR_PNPM_MISSING_TIME from version-spec cache fix(npm-resolver): minimumReleaseAge handling for cached abbreviated metadata May 14, 2026
@mandarini

Copy link
Copy Markdown
Contributor Author

Heey I think I found it @zkochan! When pnpm gets a 304 Not Modified for a package whose disk cache holds abbreviated metadata, it reuses that cached form as-is and never asks the registry for the full document. The downstream pickMatchingVersionFinal then hits its warn-and-skip path because time isn't there, so minimumReleaseAge is silently bypassed for every package in that bucket. So I added a small fix, a helper that checks the standard upgrade conditions, re-fetches with fullMetadata: true, and persists the upgraded meta to disk so the next install skips the fetch.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@resolving/npm-resolver/src/pickPackage.ts`:
- Around line 183-221: The abbreviated-metadata upgrade currently only runs for
304 responses but misses non-304 cache hits and other early-return code paths
(ctx.metaCache, preferOffline / pickLowestVersion) which call
pickMatchingVersionFinal on abbreviated metadata and can trigger
minimumReleaseAge / ERR_PNPM_MISSING_TIME; update those paths to use
maybeUpgradeAbbreviatedMetaForReleaseAge(spec, opts, meta) or, if meta.time is
missing, synchronously fetch full metadata via ctx.fetch(spec.name, {
fullMetadata: true, authHeaderValue: opts.authHeaderValue, registry:
opts.registry }) before calling pickMatchingVersionFinal (and ensure the
upgraded FetchMetadataResult is passed through so upgradedFrom is preserved), so
any branch that would operate on abbreviated meta (including preferOffline,
pickLowestVersion and ctx.metaCache usage) retries with fullMetadata when time
is absent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 21a3565f-0fc1-4a39-83bd-4caef4e3a823

📥 Commits

Reviewing files that changed from the base of the PR and between 22501e2 and aa75058.

📒 Files selected for processing (3)
  • .changeset/fix-minimum-release-age-cached-abbreviated-metadata.md
  • resolving/npm-resolver/src/pickPackage.ts
  • resolving/npm-resolver/test/publishedBy.test.ts
✅ Files skipped from review due to trivial changes (1)
  • .changeset/fix-minimum-release-age-cached-abbreviated-metadata.md
📜 Review details
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{js,ts,tsx,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,tsx,jsx}: Use trailing commas in code following Standard Style with modifications
Prefer functions over classes
Declare functions after they are used, relying on hoisting
Functions should have no more than two or three arguments; use a single options object for functions needing more parameters
Organize imports in order: standard libraries, external dependencies (sorted alphabetically), then relative imports

Files:

  • resolving/npm-resolver/test/publishedBy.test.ts
  • resolving/npm-resolver/src/pickPackage.ts
**/*.test.{js,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Do not use instanceof Error for error type checking in Jest tests; use util.types.isNativeError() instead to ensure compatibility across realms

Files:

  • resolving/npm-resolver/test/publishedBy.test.ts
🔇 Additional comments (3)
resolving/npm-resolver/src/pickPackage.ts (1)

309-316: LGTM!

Also applies to: 374-395

resolving/npm-resolver/test/publishedBy.test.ts (2)

323-378: LGTM!


437-439: ⚡ Quick win

The retryLoadJsonFile() function is explicitly NDJSON-aware via parseNdjsonMeta(), which correctly parses the second line (body) while prepareJsonForDisk() writes the two-line format. The assertion properly validates the persisted metadata and will correctly read the time field from the body, not the headers.

			> Likely an incorrect or invalid review comment.

Comment thread resolving/npm-resolver/src/pickPackage.ts Outdated
Copilot AI review requested due to automatic review settings May 14, 2026 07:56

Copilot AI 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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.

Comment thread resolving/npm-resolver/src/pickPackage.ts
Comment thread resolving/npm-resolver/src/pickPackage.ts Outdated
Comment thread resolving/npm-resolver/src/pickPackage.ts Outdated
Comment thread resolving/npm-resolver/src/pickPackage.ts Outdated
Comment thread resolving/npm-resolver/test/publishedBy.test.ts
Comment thread resolving/npm-resolver/src/pickPackage.ts Outdated
@zkochan zkochan force-pushed the fix/npm-resolver-missing-time-version-spec-cache branch from 4d84184 to dbdbc3f Compare May 14, 2026 08:31
@github-actions

Copy link
Copy Markdown
Contributor

Benchmark Results

# Scenario main branch
1 Headless (warm store+cache) 1.818s ± 0.043s 1.863s ± 0.073s
2 Re-resolution (add dep, warm) 2.458s ± 0.026s 2.493s ± 0.029s
3 Full resolution (warm, no lockfile) 4.676s ± 0.038s 4.728s ± 0.052s
4 Headless (cold store+cache) 4.633s ± 0.105s 4.648s ± 0.115s
5 Cold install (nothing warm) 9.446s ± 0.354s 9.341s ± 0.146s
6 GVS warm reinstall (warm global store) 0.939s ± 0.058s 0.924s ± 0.017s

Run 25849991479 · 10 runs per scenario · triggered by @zkochan

zkochan added 2 commits May 14, 2026 10:52
- Extract `persistUpgradedMeta` helper and call it from all three sites
  (in-memory cache hit, preferOffline disk-cache hit, 304 path) so a fresh
  process doesn't repeat the upgrade fetch.
- Forward `etag`/`modified` to the upgrade fetch in
  `maybeUpgradeAbbreviatedMetaForReleaseAge` so the registry can answer 304.
- Extract `shouldRethrowFromFastPathCache` so the two fast-path catch sites
  can't drift on the MISSING_TIME-vs-strict invariant.
- Document the deliberate choice to upgrade-fetch when `meta.modified` is
  absent or unparseable (correctness over saving a network call).
- Add a companion test that exercises the catch fix with the default
  `ignoreMissingTimeField` so the invariant holds regardless of that flag.
- Fix the existing `bareSpecifier: '3.1.0'` test setup: 3.1.0 was published
  2016-01-11, after the test's `publishedBy` of 2015-08-17, so strict mode
  correctly rejected it. Switch to 3.0.0 (released 2015-07-10).
Copilot AI review requested due to automatic review settings May 14, 2026 08:55

Copilot AI 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.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@resolving/npm-resolver/test/publishedBy.test.ts`:
- Around line 350-354: The multiline fs.writeFileSync calls (e.g., the call that
writes to cachePath using cachedMeta) are missing trailing commas on the final
argument; update those calls (including the similar call around lines 395-399)
to add a trailing comma after the 'utf8' argument so they conform to the
project's trailing-comma style rule (locate the fs.writeFileSync(...)
invocations that reference cachePath and cachedMeta and append the comma).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 91833fdf-da85-4bd4-857d-c4e02ea77daf

📥 Commits

Reviewing files that changed from the base of the PR and between dbdbc3f and c4449e4.

📒 Files selected for processing (2)
  • resolving/npm-resolver/src/pickPackage.ts
  • resolving/npm-resolver/test/publishedBy.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • resolving/npm-resolver/src/pickPackage.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{js,ts,tsx,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,tsx,jsx}: Use trailing commas in code following Standard Style with modifications
Prefer functions over classes
Declare functions after they are used, relying on hoisting
Functions should have no more than two or three arguments; use a single options object for functions needing more parameters
Organize imports in order: standard libraries, external dependencies (sorted alphabetically), then relative imports

Files:

  • resolving/npm-resolver/test/publishedBy.test.ts
**/*.test.{js,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Do not use instanceof Error for error type checking in Jest tests; use util.types.isNativeError() instead to ensure compatibility across realms

Files:

  • resolving/npm-resolver/test/publishedBy.test.ts
🔇 Additional comments (1)
resolving/npm-resolver/test/publishedBy.test.ts (1)

13-13: LGTM!

Also applies to: 323-349, 356-394, 401-480

Comment thread resolving/npm-resolver/test/publishedBy.test.ts
@zkochan zkochan enabled auto-merge (squash) May 14, 2026 10:44
@zkochan zkochan disabled auto-merge May 14, 2026 10:48
@zkochan

zkochan commented May 14, 2026

Copy link
Copy Markdown
Member

@coderabbitai approve

@coderabbitai

coderabbitai Bot commented May 14, 2026

Copy link
Copy Markdown
✅ Actions performed

Comments resolved and changes approved.

@zkochan zkochan merged commit e526f89 into pnpm:main May 14, 2026
10 checks passed
@welcome

welcome Bot commented May 14, 2026

Copy link
Copy Markdown

Congrats on merging your first pull request! 🎉🎉🎉

@mandarini

Copy link
Copy Markdown
Contributor Author

Thank you @zkochan

zkochan added a commit that referenced this pull request May 14, 2026
…metadata (#11630)

Backport of #11622 to release/10. The npm registry returns abbreviated
package metadata (without per-version `time`) by default, which made the
maturity check throw ERR_PNPM_MISSING_TIME whenever cached abbreviated
metadata was reused under `publishedBy`/`minimumReleaseAge`. pnpm now
upgrades cached abbreviated metadata to the full document via a follow-up
fetch, persists the upgrade to the on-disk cache so subsequent installs
skip the extra fetch, and lets ERR_PNPM_MISSING_TIME from the cache fast
path fall through to the network fetch even under strict mode.

Adapted to release/10's simpler pickPackage shape (no 304/etag plumbing,
no `pickMatchingVersionFast`/`pickMatchingVersionFinal` split, no
`ignoreMissingTimeField`), so the unconditional helper triggers the
upgrade whenever the cached meta lacks `time`.
mandarini added a commit to supabase/ssr that referenced this pull request May 14, 2026
Update pnpm to 11.1.2 so that it contains [the
fix](pnpm/pnpm#11622) for the "missing time"
bug.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

lodash missing time field message when npm registry includes time field

3 participants