Skip to content

fix: skip lockfile minimumReleaseAge/trustPOlicy verification for non-registry tarball#12122

Merged
zkochan merged 2 commits into
mainfrom
fix/skip-minimumReleaseAge-for-non-registry-tarball
Jun 2, 2026
Merged

fix: skip lockfile minimumReleaseAge/trustPOlicy verification for non-registry tarball#12122
zkochan merged 2 commits into
mainfrom
fix/skip-minimumReleaseAge-for-non-registry-tarball

Conversation

@btea

@btea btea commented Jun 2, 2026

Copy link
Copy Markdown
Member

close #12111

Summary by CodeRabbit

  • Bug Fixes
    • Local tarball (file:) dependencies are excluded from registry verification; tarball validation now only applies to HTTP(S) sources and non-HTTP(S) tarball URLs are skipped without rejecting entries.
  • Tests
    • Added tests confirming local/offline tarball resolutions are short-circuited and skip minimum-age/trust checks.

@btea btea requested a review from zkochan as a code owner June 2, 2026 02:46
Copilot AI review requested due to automatic review settings June 2, 2026 02:46
@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: bef095ce-6c08-4e83-b66f-bd4818314ad5

📥 Commits

Reviewing files that changed from the base of the PR and between 680615f and 4572963.

📒 Files selected for processing (5)
  • .changeset/short-lamps-relax.md
  • pacquet/crates/resolving-npm-resolver/src/create_npm_resolution_verifier.rs
  • pacquet/crates/resolving-npm-resolver/src/create_npm_resolution_verifier/tests.rs
  • resolving/npm-resolver/src/createNpmResolutionVerifier.ts
  • resolving/npm-resolver/test/createNpmResolutionVerifier.test.ts
✅ Files skipped from review due to trivial changes (1)
  • .changeset/short-lamps-relax.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • pacquet/crates/resolving-npm-resolver/src/create_npm_resolution_verifier/tests.rs
  • resolving/npm-resolver/src/createNpmResolutionVerifier.ts
  • resolving/npm-resolver/test/createNpmResolutionVerifier.test.ts
📜 Recent review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: Agent
  • GitHub Check: Lint and Test (macos-latest)
  • GitHub Check: Doc
  • GitHub Check: Lint and Test (ubuntu-latest)
  • GitHub Check: Lint and Test (windows-latest)
  • GitHub Check: Dylint
  • GitHub Check: Code Coverage
  • GitHub Check: Analyze (javascript)
  • GitHub Check: Run benchmark on ubuntu-latest
  • GitHub Check: Run benchmark on ubuntu-latest
  • GitHub Check: Compile & Lint
🧰 Additional context used
📓 Path-based instructions (1)
pacquet/**/*.rs

📄 CodeRabbit inference engine (pacquet/AGENTS.md)

pacquet/**/*.rs: Log emissions are part of matching pnpm — when porting a function that fires pnpm:<channel> events through globalLogger, logger.debug(...), or streamParser.write(...), mirror the call site, payload, and ordering so @pnpm/cli.default-reporter parses pacquet's NDJSON the same way
Declare a newtype wrapper for branded string types instead of collapsing the brand into a plain String or &str in Rust
If upstream TypeScript always validates before construction of a branded string, validate in the Rust wrapper too via TryFrom<String> and/or FromStr and do not provide an infallible public constructor
If upstream TypeScript never validates a branded string, just brand for type-safety in Rust by exposing an infallible From<String> constructor
If upstream TypeScript occasionally constructs a branded string without validation, expose from_str_unchecked in Rust as an escape hatch alongside the validating constructor
Match upstream serde behavior for branded strings crossing JSON, YAML, or INI boundaries by using #[serde(try_from = "String")] for deserialization and #[serde(into = "String")] for serialization
Derive simple conversions for branded strings using #[derive(derive_more::From)] and #[derive(derive_more::Into)] instead of handwriting impl blocks; use manual impl only when conversion needs custom logic
Model TypeScript string literal unions (like 'auto' | 'always' | 'never') as Rust enums instead of newtype wrappers, since the set of valid values is closed
Treat TypeScript string template literal types (like `${string}@${string}`) the same as branded string types in Rust, using a newtype wrapper with validation
Follow the code style guide in CODE_STYLE_GUIDE.md — imports, modules, naming, ownership and borrowing, parameter type selection, trait bounds, pattern matching, pipe-trait, error handling, test layout, and cloning of Arc and Rc
Choose owned vs. borrowed parameters to minimize copies; widen to t...

Files:

  • pacquet/crates/resolving-npm-resolver/src/create_npm_resolution_verifier.rs
🧠 Learnings (3)
📚 Learning: 2026-05-20T19:40:55.051Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11774
File: pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs:0-0
Timestamp: 2026-05-20T19:40:55.051Z
Learning: In the pacquet Rust code, ensure the semver implementation uses the `node-semver` crate (not `nodejs-semver`). `node-semver`’s public API does not include a `satisfies_with_prerelease`-style method; prerelease-tolerant matching should be implemented inline by first calling `Range::satisfies`, and when it rejects a prerelease version, retry matching against a stripped `MAJOR.MINOR.PATCH` base of the prerelease version.

Applied to files:

  • pacquet/crates/resolving-npm-resolver/src/create_npm_resolution_verifier.rs
📚 Learning: 2026-05-22T00:08:44.646Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11837
File: pacquet/crates/resolving-npm-resolver/src/pick_package.rs:33-51
Timestamp: 2026-05-22T00:08:44.646Z
Learning: In the pnpm/pnpm repo’s pacquet Rust crates, do not flag Unicode ellipsis characters (U+2026, `…`) in Rust doc comments (`///` / `/** */`) as a lint violation. The pacquet crate’s `dylint.toml` only enables `perfectionist::derive_ordering`, and the Dylint `unicode-ellipsis` rule is not enabled for this project—so `…` in doc comments is an intentional, repo-consistent style.

Applied to files:

  • pacquet/crates/resolving-npm-resolver/src/create_npm_resolution_verifier.rs
📚 Learning: 2026-05-20T23:07:58.444Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11784
File: pacquet/crates/resolving-deps-resolver/src/hoist_peers.rs:120-133
Timestamp: 2026-05-20T23:07:58.444Z
Learning: When reviewing code in this pacquet Rust port, follow the upstream pnpm compatibility rule: only match pnpm’s behavior exactly. Do not propose review changes that intentionally deviate from pnpm’s documented/observed behavior, even if pnpm appears buggy. If you identify a real bug in pnpm behavior, the review should prioritize fixing it upstream in pnpm first, and avoid implementing a pnpm-behavior workaround here unless the same fix has already landed upstream.

Applied to files:

  • pacquet/crates/resolving-npm-resolver/src/create_npm_resolution_verifier.rs
🔇 Additional comments (1)
pacquet/crates/resolving-npm-resolver/src/create_npm_resolution_verifier.rs (1)

760-765: LGTM!


📝 Walkthrough

Walkthrough

This PR prevents minimumReleaseAge and trustPolicy verification for tarball resolutions whose tarball URL uses non-HTTP(S) schemes (e.g., file:). Both TypeScript and Rust resolvers now parse tarball URLs and skip registry verification for non-http/https protocols; tests and a changeset were added.

Changes

Skip minimumReleaseAge verification for file: tarball protocols

Layer / File(s) Summary
TypeScript: Skip non-HTTP tarball verification
resolving/npm-resolver/src/createNpmResolutionVerifier.ts, resolving/npm-resolver/test/createNpmResolutionVerifier.test.ts
isNpmRegistryResolution parses the tarball URL and returns false when the protocol is explicitly present and not http: or https:, preventing file: and other non-web protocols from triggering minimumReleaseAge checks. New test verifies file: tarballs return { ok: true } without verification.
Rust: Skip non-HTTP tarball verification
pacquet/crates/resolving-npm-resolver/src/create_npm_resolution_verifier.rs, pacquet/crates/resolving-npm-resolver/src/create_npm_resolution_verifier/tests.rs
npm_registry_tarball parses the tarball URL and only accepts http or https schemes; non-HTTP(S) URLs return None, bypassing registry verification. New test constructs a file: tarball with minimumReleaseAge set and asserts the verifier short-circuits to Ok.
Release notes
.changeset/short-lamps-relax.md
Changelog entry documents the patch updates for both packages and the behavior change bypassing minimumReleaseAge/trustPolicy verification for non-registry tarball protocols.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • pnpm/pnpm#11704: Both PRs modify the npm-resolver minimumReleaseAge verification flow in createNpmResolutionVerifier.ts; #11704 changes publish timestamp retrieval while this PR adds URL-scheme validation to skip verification for file: resolutions.

Suggested reviewers

  • zkochan

Poem

🐰 A tarball from file: now skips the registry dance,
No verification checks—local builds get their chance.
Rust and TypeScript both nod and agree,
Local deps stay local, from lockfile set free. 🎩📦

🚥 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 describes the main change: skipping lockfile verification (minimumReleaseAge/trustPolicy) for non-registry tarballs, which directly addresses the PR's objective.
Linked Issues check ✅ Passed The PR successfully addresses issue #12111 by skipping minimumReleaseAge/trustPolicy verification for non-registry tarball protocols (file:), preventing local tarball dependencies from triggering registry validation errors.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the objective: adding protocol guards to skip registry verification for non-HTTP(S) tarballs, with supporting tests and a changeset entry.
Docstring Coverage ✅ Passed Docstring coverage is 100.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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/skip-minimumReleaseAge-for-non-registry-tarball

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Review Summary by Qodo

Skip verification for non-registry tarball protocols

🐞 Bug fix

Grey Divider

Walkthroughs

Description
• Skip minimumReleaseAge/trustPolicy verification for non-registry tarball protocols
• Add URL scheme validation to detect local and non-HTTP(S) tarballs
• Prevent incorrect npm registry metadata checks on local dependencies
• Add test coverage for file: protocol tarball resolutions
Diagram
flowchart LR
  A["Tarball Resolution"] --> B{"Check URL Scheme"}
  B -->|"file: or other non-HTTP(S)"| C["Skip Verification"]
  B -->|"http: or https:"| D["Perform Verification"]
  C --> E["Return Ok"]
  D --> F["Check Registry Metadata"]

Loading

Grey Divider

File Changes

1. pacquet/crates/resolving-npm-resolver/src/create_npm_resolution_verifier.rs 🐞 Bug fix +6/-0

Add URL scheme validation for tarball verification

• Added URL scheme validation to detect non-HTTP(S) tarball protocols
• Parses tarball URL and skips verification for file:, ftp:, and other non-registry schemes
• Maintains existing git-hosted tarball skip logic

pacquet/crates/resolving-npm-resolver/src/create_npm_resolution_verifier.rs


2. pacquet/crates/resolving-npm-resolver/src/create_npm_resolution_verifier/tests.rs 🧪 Tests +18/-0

Add test for file protocol tarball skipping

• Added test case for file: protocol tarball resolutions
• Verifies that minimumReleaseAge checks are skipped for local tarballs
• Tests with a realistic file path pattern

pacquet/crates/resolving-npm-resolver/src/create_npm_resolution_verifier/tests.rs


3. resolving/npm-resolver/src/createNpmResolutionVerifier.ts 🐞 Bug fix +5/-0

Add protocol validation for tarball resolutions

• Added URL protocol validation in isNpmRegistryResolution function
• Skips verification for non-HTTP(S) protocols like file:
• Uses tryParseUrl helper to safely parse tarball URLs

resolving/npm-resolver/src/createNpmResolutionVerifier.ts


View more (2)
4. resolving/npm-resolver/test/createNpmResolutionVerifier.test.ts 🧪 Tests +14/-0

Add test for file protocol tarball handling

• Added test case for file: protocol tarball resolutions
• Verifies minimumReleaseAge verification is skipped for local tarballs
• Tests with realistic package name and tarball path

resolving/npm-resolver/test/createNpmResolutionVerifier.test.ts


5. .changeset/short-lamps-relax.md 📝 Documentation +6/-0

Add changeset for tarball verification fix

• Created changeset entry documenting the bug fix
• Marks changes as patch version for affected packages
• Explains that local tarball dependencies are no longer checked against npm registry metadata

.changeset/short-lamps-relax.md


Grey Divider

Qodo Logo

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

This PR fixes an install-time verification bug where lockfile entries for local tarball dependencies (e.g. file:) were incorrectly treated as npm-registry tarballs and subjected to minimumReleaseAge / trustPolicy checks, which can trigger unnecessary registry metadata requests and failures (closes #12111).

Changes:

  • Treat tarball resolutions with non-HTTP(S) URL protocols (e.g. file:) as non-registry resolutions so verification is skipped.
  • Add regression tests in both the TypeScript resolver and the Pacquet Rust implementation.
  • Add a changeset to ship the fix as a patch for pnpm and @pnpm/resolving.npm-resolver.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
resolving/npm-resolver/src/createNpmResolutionVerifier.ts Skips verification for tarballs whose parsed URL protocol is not http:/https:.
resolving/npm-resolver/test/createNpmResolutionVerifier.test.ts Adds a regression test ensuring file: tarball resolutions are not verified.
pacquet/crates/resolving-npm-resolver/src/create_npm_resolution_verifier.rs Mirrors the protocol-based skip logic in the Rust verifier (http/https only).
pacquet/crates/resolving-npm-resolver/src/create_npm_resolution_verifier/tests.rs Adds a regression test ensuring file: tarball resolutions short-circuit to Ok.
.changeset/short-lamps-relax.md Publishes the behavior change as a patch release.

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

Comment thread resolving/npm-resolver/src/createNpmResolutionVerifier.ts
@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Micro-Benchmark Results

Linux

group                          main                                   pr
-----                          ----                                   --
tarball/download_dependency    1.01      7.6±0.23ms   572.5 KB/sec    1.00      7.5±0.24ms   575.6 KB/sec

@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.

🧹 Nitpick comments (1)
pacquet/crates/resolving-npm-resolver/src/create_npm_resolution_verifier/tests.rs (1)

240-241: ⚡ Quick win

Drop the prose comment and let the test name carry the scenario.

These lines just restate what verify_short_circuits_file_tarball_resolution() already says and what the assertion proves. As per coding guidelines, "Tests are documentation — do not duplicate test scenarios, edge cases, failure modes, or worked examples in prose when they are already captured by tests."

🤖 Prompt for 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.

In
`@pacquet/crates/resolving-npm-resolver/src/create_npm_resolution_verifier/tests.rs`
around lines 240 - 241, Remove the redundant prose comment above the test:
delete the two-line comment block that explains the `file:` tarball resolution
behavior so the test name `verify_short_circuits_file_tarball_resolution()`
stands alone; ensure no other tests or comments rely on that text and leave the
test and its assertions unchanged.
🤖 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.

Nitpick comments:
In
`@pacquet/crates/resolving-npm-resolver/src/create_npm_resolution_verifier/tests.rs`:
- Around line 240-241: Remove the redundant prose comment above the test: delete
the two-line comment block that explains the `file:` tarball resolution behavior
so the test name `verify_short_circuits_file_tarball_resolution()` stands alone;
ensure no other tests or comments rely on that text and leave the test and its
assertions unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 77531a7a-4d78-4a4b-94e3-76b48beea62d

📥 Commits

Reviewing files that changed from the base of the PR and between 5c669d7 and 0eb0b7d.

📒 Files selected for processing (5)
  • .changeset/short-lamps-relax.md
  • pacquet/crates/resolving-npm-resolver/src/create_npm_resolution_verifier.rs
  • pacquet/crates/resolving-npm-resolver/src/create_npm_resolution_verifier/tests.rs
  • resolving/npm-resolver/src/createNpmResolutionVerifier.ts
  • resolving/npm-resolver/test/createNpmResolutionVerifier.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: Agent
  • GitHub Check: Code Coverage
  • GitHub Check: Lint and Test (ubuntu-latest)
  • GitHub Check: Lint and Test (windows-latest)
  • GitHub Check: Run benchmark on ubuntu-latest
  • GitHub Check: Dylint
  • GitHub Check: Lint and Test (macos-latest)
  • GitHub Check: Run benchmark on ubuntu-latest
  • GitHub Check: Analyze (javascript)
  • GitHub Check: Compile & Lint
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Follow Standard Style with trailing commas, preferring functions over classes, and declaring functions after they are used (relying on hoisting)
Use a single options object instead of multiple parameters when a function needs more than two or three arguments
Follow Import Order: Standard libraries first, then external dependencies (alphabetically), then relative imports
Write self-documenting code where function names, parameters, and types explain what a function does without requiring prose comments
Do not write comments that restate what the code already says; refactor via renaming, splitting helpers, or restructuring instead
Do not repeat documentation at call sites that already exists in JSDoc on the callee; update JSDoc once for all call sites to benefit
Use JSDoc only for a function's contract (preconditions, postconditions, edge cases, why the function exists), not for re-narrating the body
Do not record past implementation shape, refactor history, or 'the previous code did X' framing in code; use git log and git blame instead
Write comments only when: the reason for code is non-obvious (hidden invariant, workaround for known bug, deliberate exception), or the right name doesn't fit (temporary technical constraint)

Files:

  • resolving/npm-resolver/test/createNpmResolutionVerifier.test.ts
  • resolving/npm-resolver/src/createNpmResolutionVerifier.ts
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use util.types.isNativeError() instead of instanceof Error for error type checking in Jest tests

Files:

  • resolving/npm-resolver/test/createNpmResolutionVerifier.test.ts
pacquet/**/*.rs

📄 CodeRabbit inference engine (pacquet/AGENTS.md)

pacquet/**/*.rs: Log emissions are part of matching pnpm — when porting a function that fires pnpm:<channel> events through globalLogger, logger.debug(...), or streamParser.write(...), mirror the call site, payload, and ordering so @pnpm/cli.default-reporter parses pacquet's NDJSON the same way
Declare a newtype wrapper for branded string types instead of collapsing the brand into a plain String or &str in Rust
If upstream TypeScript always validates before construction of a branded string, validate in the Rust wrapper too via TryFrom<String> and/or FromStr and do not provide an infallible public constructor
If upstream TypeScript never validates a branded string, just brand for type-safety in Rust by exposing an infallible From<String> constructor
If upstream TypeScript occasionally constructs a branded string without validation, expose from_str_unchecked in Rust as an escape hatch alongside the validating constructor
Match upstream serde behavior for branded strings crossing JSON, YAML, or INI boundaries by using #[serde(try_from = "String")] for deserialization and #[serde(into = "String")] for serialization
Derive simple conversions for branded strings using #[derive(derive_more::From)] and #[derive(derive_more::Into)] instead of handwriting impl blocks; use manual impl only when conversion needs custom logic
Model TypeScript string literal unions (like 'auto' | 'always' | 'never') as Rust enums instead of newtype wrappers, since the set of valid values is closed
Treat TypeScript string template literal types (like `${string}@${string}`) the same as branded string types in Rust, using a newtype wrapper with validation
Follow the code style guide in CODE_STYLE_GUIDE.md — imports, modules, naming, ownership and borrowing, parameter type selection, trait bounds, pattern matching, pipe-trait, error handling, test layout, and cloning of Arc and Rc
Choose owned vs. borrowed parameters to minimize copies; widen to t...

Files:

  • pacquet/crates/resolving-npm-resolver/src/create_npm_resolution_verifier/tests.rs
  • pacquet/crates/resolving-npm-resolver/src/create_npm_resolution_verifier.rs
🧠 Learnings (6)
📚 Learning: 2026-05-14T09:04:00.133Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11622
File: resolving/npm-resolver/test/publishedBy.test.ts:350-354
Timestamp: 2026-05-14T09:04:00.133Z
Learning: In the pnpm/pnpm repository, ESLint is the authoritative style linter. Do not raise review findings for missing trailing commas in multiline function calls (e.g., `fs.writeFileSync(...)`) when this repo’s ESLint configuration does not report them and lint passes. Prefer deferring to the ESLint results for this specific trailing-comma rule rather than enforcing it manually in code review.

Applied to files:

  • resolving/npm-resolver/test/createNpmResolutionVerifier.test.ts
  • resolving/npm-resolver/src/createNpmResolutionVerifier.ts
📚 Learning: 2026-05-20T19:40:55.051Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11774
File: pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs:0-0
Timestamp: 2026-05-20T19:40:55.051Z
Learning: In the pacquet Rust code, ensure the semver implementation uses the `node-semver` crate (not `nodejs-semver`). `node-semver`’s public API does not include a `satisfies_with_prerelease`-style method; prerelease-tolerant matching should be implemented inline by first calling `Range::satisfies`, and when it rejects a prerelease version, retry matching against a stripped `MAJOR.MINOR.PATCH` base of the prerelease version.

Applied to files:

  • pacquet/crates/resolving-npm-resolver/src/create_npm_resolution_verifier/tests.rs
  • pacquet/crates/resolving-npm-resolver/src/create_npm_resolution_verifier.rs
📚 Learning: 2026-05-22T00:08:44.646Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11837
File: pacquet/crates/resolving-npm-resolver/src/pick_package.rs:33-51
Timestamp: 2026-05-22T00:08:44.646Z
Learning: In the pnpm/pnpm repo’s pacquet Rust crates, do not flag Unicode ellipsis characters (U+2026, `…`) in Rust doc comments (`///` / `/** */`) as a lint violation. The pacquet crate’s `dylint.toml` only enables `perfectionist::derive_ordering`, and the Dylint `unicode-ellipsis` rule is not enabled for this project—so `…` in doc comments is an intentional, repo-consistent style.

Applied to files:

  • pacquet/crates/resolving-npm-resolver/src/create_npm_resolution_verifier/tests.rs
  • pacquet/crates/resolving-npm-resolver/src/create_npm_resolution_verifier.rs
📚 Learning: 2026-05-20T23:07:58.444Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11784
File: pacquet/crates/resolving-deps-resolver/src/hoist_peers.rs:120-133
Timestamp: 2026-05-20T23:07:58.444Z
Learning: When reviewing code in this pacquet Rust port, follow the upstream pnpm compatibility rule: only match pnpm’s behavior exactly. Do not propose review changes that intentionally deviate from pnpm’s documented/observed behavior, even if pnpm appears buggy. If you identify a real bug in pnpm behavior, the review should prioritize fixing it upstream in pnpm first, and avoid implementing a pnpm-behavior workaround here unless the same fix has already landed upstream.

Applied to files:

  • pacquet/crates/resolving-npm-resolver/src/create_npm_resolution_verifier/tests.rs
  • pacquet/crates/resolving-npm-resolver/src/create_npm_resolution_verifier.rs
📚 Learning: 2026-05-23T17:29:56.247Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11878
File: resolving/npm-resolver/src/createNpmResolutionVerifier.ts:381-418
Timestamp: 2026-05-23T17:29:56.247Z
Learning: When reviewing the npm resolver code, note that `PackageMetaCache` is intentionally keyed only by `name` and `name:full` (no registry component). As a result, code that shares this cache (e.g., `createNpmResolutionVerifier.ts` via the shared `validateSharedMeta` guard) can prevent cross-package contamination by matching names, but it cannot distinguish two different registries that serve packages with the same name within a single install. Don’t flag (or attempt to fix) this as a local issue in the verifier alone—correctly distinguishing registries would require a coordinated change to the resolver cache key shape. (The Pacquet/Rust cache is already registry-qualified, unlike the npm-resolver cache.)

Applied to files:

  • resolving/npm-resolver/src/createNpmResolutionVerifier.ts
📚 Learning: 2026-05-26T21:01:06.666Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11966
File: .changeset/require-tarball-integrity.md:6-6
Timestamp: 2026-05-26T21:01:06.666Z
Learning: In pnpm lockfile-related release notes/docs (especially changeset markdown), preserve URL hostnames exactly as they appear in pnpm-lock.yaml tarball resolution entries—keep hosts like `codeload.github.com`, `bitbucket.org`, and `gitlab.com` in lowercase. Do not “correct” them to title-case/preserve brand capitalization (e.g., LanguageTool rules like `GITHUB` capitalization) because these are literal URL fragments, not platform brand names.

Applied to files:

  • .changeset/short-lamps-relax.md
🔇 Additional comments (5)
resolving/npm-resolver/src/createNpmResolutionVerifier.ts (1)

833-837: LGTM!

resolving/npm-resolver/test/createNpmResolutionVerifier.test.ts (1)

224-236: LGTM!

pacquet/crates/resolving-npm-resolver/src/create_npm_resolution_verifier.rs (1)

760-765: LGTM!

pacquet/crates/resolving-npm-resolver/src/create_npm_resolution_verifier/tests.rs (1)

242-256: LGTM!

.changeset/short-lamps-relax.md (1)

1-6: LGTM!

@codecov-commenter

codecov-commenter commented Jun 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 83.33333% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 87.51%. Comparing base (ba2bacd) to head (4572963).

Files with missing lines Patch % Lines
...npm-resolver/src/create_npm_resolution_verifier.rs 83.33% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main   #12122   +/-   ##
=======================================
  Coverage   87.51%   87.51%           
=======================================
  Files         261      261           
  Lines       29640    29646    +6     
=======================================
+ Hits        25939    25945    +6     
  Misses       3701     3701           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Integrated-Benchmark Report (Linux)

Scenario: Isolated linker: fresh restore, cold cache + cold store

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 2.072 ± 0.124 1.960 2.375 1.01 ± 0.06
pacquet@main 2.049 ± 0.045 1.979 2.126 1.00
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 2.0719092614599997,
      "stddev": 0.12430612919303112,
      "median": 2.03098830676,
      "user": 2.6108468799999995,
      "system": 3.35705324,
      "min": 1.95967425976,
      "max": 2.3753229977599997,
      "times": [
        2.02524019976,
        2.03673641376,
        1.9836468147600002,
        2.0171981997599997,
        2.0629220497599996,
        1.95967425976,
        2.00558876176,
        2.19637868476,
        2.3753229977599997,
        2.0563842327599997
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 2.04944292936,
      "stddev": 0.045476903137469556,
      "median": 2.0516468017599996,
      "user": 2.64078868,
      "system": 3.3007296399999992,
      "min": 1.9789279907600001,
      "max": 2.12572006976,
      "times": [
        1.9789279907600001,
        2.0306881467599998,
        2.0299788037599997,
        2.0790153507599998,
        2.07736989376,
        2.12572006976,
        2.0335166977599997,
        1.98815075976,
        2.06977690576,
        2.08128467476
      ]
    }
  ]
}

Scenario: Isolated linker: fresh restore, hot cache + hot store

Command Mean [ms] Min [ms] Max [ms] Relative
pacquet@HEAD 642.9 ± 15.3 629.8 683.5 1.00
pacquet@main 680.5 ± 27.4 641.9 723.5 1.06 ± 0.05
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 0.64285575726,
      "stddev": 0.015310751230294978,
      "median": 0.63818573926,
      "user": 0.35854157999999997,
      "system": 1.31046392,
      "min": 0.62975821726,
      "max": 0.68345455526,
      "times": [
        0.68345455526,
        0.62975821726,
        0.63667492526,
        0.63969655326,
        0.63533578726,
        0.64117961526,
        0.64923139326,
        0.63627184826,
        0.63299629726,
        0.64395838026
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 0.68046175306,
      "stddev": 0.027419378470993672,
      "median": 0.68725019626,
      "user": 0.3675950799999999,
      "system": 1.31849362,
      "min": 0.64187849426,
      "max": 0.72349703626,
      "times": [
        0.72349703626,
        0.69215108526,
        0.66079050726,
        0.68533484926,
        0.64187849426,
        0.65794245926,
        0.70741160126,
        0.64616393726,
        0.7002820172600001,
        0.68916554326
      ]
    }
  ]
}

Scenario: Isolated linker: fresh install, cold cache + cold store

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 2.286 ± 0.029 2.238 2.343 1.00
pacquet@main 2.316 ± 0.059 2.226 2.424 1.01 ± 0.03
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 2.28617979728,
      "stddev": 0.02854882025198684,
      "median": 2.2834353838799997,
      "user": 3.7381535,
      "system": 3.06008626,
      "min": 2.23832946238,
      "max": 2.3429083293799997,
      "times": [
        2.3429083293799997,
        2.27823242338,
        2.27983821638,
        2.29054889838,
        2.3171947193799998,
        2.26120102138,
        2.28703255138,
        2.23832946238,
        2.2761363813799997,
        2.29037596938
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 2.31560608098,
      "stddev": 0.05921446417776251,
      "median": 2.3232639753799997,
      "user": 3.7798271999999997,
      "system": 3.1104397599999993,
      "min": 2.22644385138,
      "max": 2.42410525638,
      "times": [
        2.26438353538,
        2.27599062638,
        2.33551136438,
        2.2688064563799997,
        2.32456207738,
        2.22644385138,
        2.3843844113799997,
        2.32196587338,
        2.3299073573799998,
        2.42410525638
      ]
    }
  ]
}

Scenario: Isolated linker: fresh install, hot cache + hot store

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 1.463 ± 0.015 1.443 1.483 1.00
pacquet@main 1.540 ± 0.101 1.469 1.821 1.05 ± 0.07
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 1.4629965745400004,
      "stddev": 0.014557227960400616,
      "median": 1.46735931834,
      "user": 1.6605675399999995,
      "system": 1.82190786,
      "min": 1.44302641734,
      "max": 1.4826459783400001,
      "times": [
        1.44551564334,
        1.4826459783400001,
        1.44895603634,
        1.44302641734,
        1.45098452034,
        1.4776837303400001,
        1.46861840034,
        1.47538767134,
        1.47104711134,
        1.46610023634
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 1.54031804514,
      "stddev": 0.10090761781553367,
      "median": 1.51148811984,
      "user": 1.73277234,
      "system": 1.8412691600000002,
      "min": 1.46930162334,
      "max": 1.8206395803400002,
      "times": [
        1.46930162334,
        1.52980475234,
        1.49463414734,
        1.8206395803400002,
        1.50798984134,
        1.51951027634,
        1.49216488934,
        1.51498639834,
        1.5497769343399999,
        1.50437200834
      ]
    }
  ]
}

@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

🐰 Bencher Report

Branchpr/12122
Testbedpacquet
Click to view all benchmark results
BenchmarkLatencyBenchmark Result
milliseconds (ms)
(Result Δ%)
Upper Boundary
milliseconds (ms)
(Limit %)
isolated-linker.fresh-install.cold-cache.cold-store📈 view plot
🚷 view threshold
2,286.18 ms
(-2.84%)Baseline: 2,352.91 ms
2,823.50 ms
(80.97%)
isolated-linker.fresh-install.hot-cache.hot-store📈 view plot
🚷 view threshold
1,463.00 ms
(-4.19%)Baseline: 1,526.91 ms
1,832.29 ms
(79.85%)
isolated-linker.fresh-restore.cold-cache.cold-store📈 view plot
🚷 view threshold
2,071.91 ms
(+0.08%)Baseline: 2,070.26 ms
2,484.32 ms
(83.40%)
isolated-linker.fresh-restore.hot-cache.hot-store📈 view plot
🚷 view threshold
642.86 ms
(-2.63%)Baseline: 660.20 ms
792.24 ms
(81.14%)
🐰 View full continuous benchmarking report in Bencher

Copilot AI review requested due to automatic review settings June 2, 2026 08:01
@zkochan zkochan force-pushed the fix/skip-minimumReleaseAge-for-non-registry-tarball branch from 680615f to 4572963 Compare June 2, 2026 08:01

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 5 out of 5 changed files in this pull request and generated no new comments.

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

CI Feedback 🧐

A test triggered by this PR failed. Here is an AI-generated analysis of the failure:

Action: ubuntu-latest / Node.js 24 / Test

Failed stage: Run tests (affected packages) [❌]

Failed test name: test/packageManagerCheck.test.ts; test/configurationalDependencies.test.ts

Failure summary:

The action failed because the Jest test run for pnpm@11.5.1 ended with failing test suites (exit
code 1).
- FAIL test/packageManagerCheck.test.ts: two assertions expected execPnpmSync(...) to
return status 0, but got status 1 (see test/packageManagerCheck.test.ts:716 and
test/packageManagerCheck.test.ts:733). This indicates pnpm --version and pnpm install did not exit
successfully in the test environment.
- FAIL test/configurationalDependencies.test.ts: the test
packageManagerDependencies is refreshed when pnpm is invoked via corepack (#11397) failed because
pnpm could not resolve @pnpm/exe@11.5.1 from the test registry: [ERROR] No matching version found
for @pnpm/exe@11.5.1 while fetching it from http://localhost:36053/ (error surfaced via
test/utils/execPnpm.ts:56).
Note: earlier fuse-native install scripts also errored with Error: spawn
node-gyp ENOENT (missing node-gyp), but the job continued and the final workflow failure is caused
by the failing Jest suites above.

Relevant error logs:
1:  ##[group]Runner Image Provisioner
2:  Hosted Compute Agent
...

286:  Progress: resolved 1652, reused 3, downloaded 1594, added 1596
287:  Progress: resolved 1652, reused 3, downloaded 1649, added 1652, done
288:  .../node_modules/fuse-native install$ node-gyp-build
289:  .../node_modules/esbuild postinstall$ node install.js
290:  .../node_modules/ghooks install$ node ./bin/module-install
291:  .../node_modules/msgpackr-extract install$ node-gyp-build-optional-packages
292:  .../node_modules/esbuild postinstall: Done
293:  .../node_modules/ghooks install: This does not seem to be a git project.
294:  .../node_modules/ghooks install: Although ghooks was installed, the actual git hooks have not.
295:  .../node_modules/ghooks install: Run "git init" and then "npm explore ghooks -- npm run install".
296:  .../node_modules/ghooks install: 
297:  .../node_modules/ghooks install: Please ignore this message if you are not using ghooks directly.
298:  .../node_modules/ghooks install: Done
299:  .../node_modules/unrs-resolver postinstall$ node postinstall.js
300:  .../node_modules/fuse-native install: node:events:485
301:  .../node_modules/fuse-native install:       throw er; // Unhandled 'error' event
302:  .../node_modules/fuse-native install:       ^
303:  .../node_modules/fuse-native install: 
304:  .../node_modules/fuse-native install: Error: spawn node-gyp ENOENT
305:  .../node_modules/fuse-native install:     at ChildProcess._handle.onexit (node:internal/child_process:286:19)
306:  .../node_modules/fuse-native install:     at onErrorNT (node:internal/child_process:484:16)
307:  .../node_modules/fuse-native install:     at process.processTicksAndRejections (node:internal/process/task_queues:90:21)
308:  .../node_modules/fuse-native install: Emitted 'error' event on ChildProcess instance at:
309:  .../node_modules/fuse-native install:     at ChildProcess._handle.onexit (node:internal/child_process:292:12)
310:  .../node_modules/fuse-native install:     at onErrorNT (node:internal/child_process:484:16)
311:  .../node_modules/fuse-native install:     at process.processTicksAndRejections (node:internal/process/task_queues:90:21) {
312:  .../node_modules/fuse-native install:   errno: -2,
313:  .../node_modules/fuse-native install:   code: 'ENOENT',
314:  .../node_modules/fuse-native install:   syscall: 'spawn node-gyp',
315:  .../node_modules/fuse-native install:   path: 'node-gyp',
316:  .../node_modules/fuse-native install:   spawnargs: [ 'rebuild' ]
317:  .../node_modules/fuse-native install: }
318:  .../node_modules/fuse-native install: 
319:  .../node_modules/fuse-native install: Node.js v24.0.0
320:  .../node_modules/fuse-native install: Failed
321:  .../node_modules/unrs-resolver postinstall: [napi-postinstall@0.3.4] Failed to find package "@unrs/resolver-binding-linux-x64-gnu" on the file system
322:  .../node_modules/unrs-resolver postinstall: 
323:  .../node_modules/unrs-resolver postinstall: This can happen if you use the "--no-optional" flag. The "optionalDependencies"
324:  .../node_modules/unrs-resolver postinstall: package.json feature is used by unrs-resolver to install the correct napi binary
325:  .../node_modules/unrs-resolver postinstall: for your current platform. This install script will now attempt to work around
326:  .../node_modules/unrs-resolver postinstall: this. If that fails, you need to remove the "--no-optional" flag to use unrs-resolver.
327:  .../node_modules/unrs-resolver postinstall: 
328:  .../node_modules/unrs-resolver postinstall: [napi-postinstall@0.3.4] Trying to install package "@unrs/resolver-binding-linux-x64-gnu" using npm
329:  .../node_modules/unrs-resolver postinstall: [napi-postinstall@0.3.4] Failed to install package "@unrs/resolver-binding-linux-x64-gnu" using npm Cannot find module 'unrs-resolver/package.json'
330:  .../node_modules/unrs-resolver postinstall: Require stack:
...

385:  ##[endgroup]
386:  ##[group]Run npm --version
387:  �[36;1mnpm --version�[0m
388:  shell: /usr/bin/bash -e {0}
389:  env:
390:  PNPM_HOME: /home/runner/setup-pnpm/node_modules/.bin
391:  ##[endgroup]
392:  10.9.8
393:  ##[group]Run actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
394:  with:
395:  name: compiled-packages
396:  merge-multiple: false
397:  repository: pnpm/pnpm
398:  run-id: 26806685207
399:  skip-decompress: false
400:  digest-mismatch: error
401:  env:
...

599:  Cache hit for restore-key: v0-rust-registry-prepare-Linux-x64-ec5417d9-ea154ed6
600:  Received 109051904 of 143328804 (76.1%), 103.9 MBs/sec
601:  Received 143328804 of 143328804 (100.0%), 99.7 MBs/sec
602:  Cache Size: ~137 MB (143328804 B)
603:  [command]/usr/bin/tar -xf /home/runner/work/_temp/342bcbcb-5614-465c-a827-9e364b183cbf/cache.tzst -P -C /home/runner/work/pnpm/pnpm --use-compress-program unzstd
604:  Cache restored successfully
605:  Restored from cache key "v0-rust-registry-prepare-Linux-x64-ec5417d9-ea154ed6" full match: false.
606:  ##[group]Run cargo build --locked --release -p pnpr-fixtures --bin pnpr-prepare
607:  �[36;1mcargo build --locked --release -p pnpr-fixtures --bin pnpr-prepare�[0m
608:  �[36;1mbin="$PWD/target/release/pnpr-prepare"�[0m
609:  �[36;1m[ -f "$bin.exe" ] && bin="$bin.exe"�[0m
610:  �[36;1mecho "PNPR_PREPARE_BIN=$bin" >> "$GITHUB_ENV"�[0m
611:  shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0}
612:  env:
613:  PNPM_HOME: /home/runner/setup-pnpm/node_modules/.bin
614:  CACHE_ON_FAILURE: false
615:  CARGO_INCREMENTAL: 0
...

622:  �[36;1m  echo "script=ci:test-all" >> "$GITHUB_OUTPUT"�[0m
623:  �[36;1m  echo "scope=all" >> "$GITHUB_OUTPUT"�[0m
624:  �[36;1melse�[0m
625:  �[36;1m  git remote set-branches --add origin main && git fetch origin main --depth=1�[0m
626:  �[36;1m  if [ -n "$(git diff --name-only origin/main HEAD -- pnpm-workspace.yaml)" ]; then�[0m
627:  �[36;1m    echo "script=ci:test-all" >> "$GITHUB_OUTPUT"�[0m
628:  �[36;1m    echo "scope=all — pnpm-workspace.yaml modified" >> "$GITHUB_OUTPUT"�[0m
629:  �[36;1m  else�[0m
630:  �[36;1m    echo "script=ci:test-branch" >> "$GITHUB_OUTPUT"�[0m
631:  �[36;1m    echo "scope=affected packages" >> "$GITHUB_OUTPUT"�[0m
632:  �[36;1m  fi�[0m
633:  �[36;1mfi�[0m
634:  shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0}
635:  env:
636:  PNPM_HOME: /home/runner/setup-pnpm/node_modules/.bin
637:  CACHE_ON_FAILURE: false
638:  CARGO_INCREMENTAL: 0
639:  PNPR_PREPARE_BIN: /home/runner/work/pnpm/pnpm/target/release/pnpr-prepare
640:  REF_NAME: fix/skip-minimumReleaseAge-for-non-registry-tarball
641:  ##[endgroup]
642:  From https://github.com/pnpm/pnpm
643:  * branch            main       -> FETCH_HEAD
644:  * [new branch]      main       -> origin/main
645:  ##[group]Run pn run "$TEST_SCRIPT"
646:  �[36;1mpn run "$TEST_SCRIPT"�[0m
647:  shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0}
648:  env:
649:  PNPM_HOME: /home/runner/setup-pnpm/node_modules/.bin
650:  CACHE_ON_FAILURE: false
651:  CARGO_INCREMENTAL: 0
...

653:  PNPM_WORKERS: 3
654:  TEST_SCRIPT: ci:test-branch
655:  ##[endgroup]
656:  Scope: all 207 workspace projects
657:  Lockfile is up to date, resolution step is skipped
658:  ▶ Using pacquet for this install
659:  pacquet is pnpm's Rust install engine (preview); declared in configDependencies.
660:  Packages: +10
661:  ++++++++++
662:  Progress: resolved 1, reused 0, downloaded 0, added 0
663:  Progress: resolved 10, reused 9, downloaded 0, added 9
664:  Progress: resolved 10, reused 9, downloaded 1, added 10
665:  Progress: resolved 10, reused 9, downloaded 1, added 10, done
666:  .../node_modules/fuse-native install$ node-gyp-build
667:  .../node_modules/fuse-native install: node:events:485
668:  .../node_modules/fuse-native install:       throw er; // Unhandled 'error' event
669:  .../node_modules/fuse-native install:       ^
670:  .../node_modules/fuse-native install: 
671:  .../node_modules/fuse-native install: Error: spawn node-gyp ENOENT
672:  .../node_modules/fuse-native install:     at ChildProcess._handle.onexit (node:internal/child_process:286:19)
673:  .../node_modules/fuse-native install:     at onErrorNT (node:internal/child_process:484:16)
674:  .../node_modules/fuse-native install:     at process.processTicksAndRejections (node:internal/process/task_queues:90:21)
675:  .../node_modules/fuse-native install: Emitted 'error' event on ChildProcess instance at:
676:  .../node_modules/fuse-native install:     at ChildProcess._handle.onexit (node:internal/child_process:292:12)
677:  .../node_modules/fuse-native install:     at onErrorNT (node:internal/child_process:484:16)
678:  .../node_modules/fuse-native install:     at process.processTicksAndRejections (node:internal/process/task_queues:90:21) {
679:  .../node_modules/fuse-native install:   errno: -2,
680:  .../node_modules/fuse-native install:   code: 'ENOENT',
681:  .../node_modules/fuse-native install:   syscall: 'spawn node-gyp',
682:  .../node_modules/fuse-native install:   path: 'node-gyp',
683:  .../node_modules/fuse-native install:   spawnargs: [ 'rebuild' ]
684:  .../node_modules/fuse-native install: }
685:  .../node_modules/fuse-native install: 
686:  .../node_modules/fuse-native install: Node.js v24.0.0
687:  .../node_modules/fuse-native install: Failed
688:  . prepare$ husky
...

827:  ✓ branch name with multiple slashes
828:  readReleased
829:  ✓ returns empty set when directory is missing (1 ms)
830:  ✓ reads ids from .txt files and merges across files (1 ms)
831:  ✓ skips comments and empty lines (1 ms)
832:  appendReleased
833:  ✓ writes ids to <branch>.txt sorted (1 ms)
834:  ✓ dedupes against existing entries on disk (1 ms)
835:  ✓ uses sanitized filename for branches with /
836:  ✓ no-op for empty list
837:  ✓ creates the released directory if missing (1 ms)
838:  hideReleased / restoreHidden / deleteHidden
839:  ✓ hides files matching released ids; leaves others alone (7 ms)
840:  ✓ restoreHidden brings them back to .md (2 ms)
841:  ✓ deleteHidden removes the .md.released files (1 ms)
842:  ✓ rolls back already-renamed files when a later rename fails (11 ms)
843:  listChangesetIds
...

1153:  Time:        7.552 s
1154:  Ran all test suites.
1155:  ##[endgroup]
1156:  ##[group]@pnpm/exec.commands@1100.2.1 : .test exec/commands
1157:  $ cross-env NODE_OPTIONS="$NODE_OPTIONS --experimental-vm-modules --disable-warning=ExperimentalWarning --disable-warning=DEP0169" jest
1158:  �[2m2026-06-02T08:08:34.803407Z�[0m �[32m INFO�[0m �[2mpnpm_registry::server�[0m�[2m:�[0m pnpm-registry listening �[3mlisten�[0m�[2m=�[0m127.0.0.1:33249
1159:  $ node "/tmp/pnpm-test-ipc-IgQm74/line.cjs" "/tmp/347d0544-6af9-4bea-a8b2-b6e17ee6a1e8.sock" "project-1"
1160:  $ node "/tmp/pnpm-test-ipc-Zxim9y/line.cjs" "/tmp/9b452c9f-c0ca-4d1d-85e3-3de607721d1e.sock" "project"
1161:  $ node /home/runner/work/pnpm/pnpm/pnpm/bin/pnpm.mjs run -r build
1162:  No projects matched the filters "/home/runner/work/pnpm_tmp/26_6379/13" in "/home/runner/work/pnpm_tmp/26_6379/13"
1163:  Scope: all 3 workspace projects
1164:  ../project-2 build$ node "/tmp/pnpm-test-ipc-WeZbSo/line.cjs" "/tmp/ad9098b6-9e10-48cd-ba96-d0cd41552fff.sock" "project-2"
1165:  ../project-3 build$ node "/tmp/pnpm-test-ipc-3xROwW/line.cjs" "/tmp/c94bc504-0642-4cf1-a718-3fcace17a4c4.sock" "project-3"
1166:  ../project-2 build: Done
1167:  ../project-3 build: Done
1168:  Jest: importing JSON without an import attribute is deprecated and will be a hard error in the next major. Update the import of "/home/runner/work/pnpm_tmp/26_6379/17/pnpm-exec-summary.json" (from /home/runner/work/pnpm/pnpm/exec/commands/test/runRecursive.ts): use `with { type: 'json' }` for static imports, or pass `{ with: { type: 'json' } }` as the second argument to dynamic `import()`.
1169:  Jest: importing JSON without an import attribute is deprecated and will be a hard error in the next major. Update the import of "/home/runner/work/pnpm_tmp/26_6379/18/pnpm-exec-summary.json" (from /home/runner/work/pnpm/pnpm/exec/commands/test/runRecursive.ts): use `with { type: 'json' }` for static imports, or pass `{ with: { type: 'json' } }` as the second argument to dynamic `import()`.
1170:  $ node -e "assert.strictEqual(process.env.NODE_OPTIONS, '--max-old-space-size=1200')"
1171:  PASS test/runRecursive.ts (14.849 s)
1172:  ● Console
1173:  console.log
1174:  recursive run does not fail when if-present is true
1175:  at Object.<anonymous> (test/runRecursive.ts:306:11)
1176:  console.log
1177:  recursive run does not fail when if-present is true
1178:  at Object.<anonymous> (test/runRecursive.ts:361:11)
1179:  console.log
1180:  prints the list of available commands if a single project is selected
1181:  at Object.<anonymous> (test/runRecursive.ts:495:11)
1182:  console.log
1183:  throws an error if several projects are selected
1184:  at Object.<anonymous> (test/runRecursive.ts:520:11)
...

1220:  > node "/tmp/pnpm-test-ipc-9PlTyC/line.cjs" "/tmp/add47ea2-3f09-4bcb-b5b5-3c0daee39664.sock" "project-1"
1221:  > project-2@1.0.0 build
1222:  > exit 1 && node "/tmp/pnpm-test-ipc-9PlTyC/line.cjs" "/tmp/add47ea2-3f09-4bcb-b5b5-3c0daee39664.sock" "project-2"
1223:  > project-3@1.0.0 build
1224:  > node "/tmp/pnpm-test-ipc-9PlTyC/line.cjs" "/tmp/add47ea2-3f09-4bcb-b5b5-3c0daee39664.sock" "project-3"
1225:  > a-dependent@1.0.0 build
1226:  > node "/tmp/pnpm-test-ipc-4IEyBz/line.cjs" "/tmp/ad65dc1b-567c-4ac0-8ce9-6e97ac5e0e07.sock" "a-dependent"
1227:  > b-dependency@1.0.0 build
1228:  > node "/tmp/pnpm-test-ipc-4IEyBz/line.cjs" "/tmp/ad65dc1b-567c-4ac0-8ce9-6e97ac5e0e07.sock" "b-dependency"
1229:  > project-2@1.0.0 build
1230:  > node "/tmp/pnpm-test-ipc-EOGSPC/line.cjs" "/tmp/970d9c15-0876-4d2b-b911-5582f4c3b3cc.sock" "project-2"
1231:  > project-3@1.0.0 build
1232:  > node "/tmp/pnpm-test-ipc-EOGSPC/line.cjs" "/tmp/970d9c15-0876-4d2b-b911-5582f4c3b3cc.sock" "project-3"
1233:  > project-1@1.0.0 build
1234:  > node "/tmp/pnpm-test-ipc-EOGSPC/line.cjs" "/tmp/970d9c15-0876-4d2b-b911-5582f4c3b3cc.sock" "project-1"
1235:  Jest: importing JSON without an import attribute is deprecated and will be a hard error in the next major. Update the import of "/home/runner/work/pnpm_tmp/27_6379/9/project/output.json" (from /home/runner/work/pnpm/pnpm/exec/commands/test/exec.e2e.ts): use `with { type: 'json' }` for static imports, or pass `{ with: { type: 'json' } }` as the second argument to dynamic `import()`.
1236:  Jest: importing JSON without an import attribute is deprecated and will be a hard error in the next major. Update the import of "/home/runner/work/pnpm_tmp/27_6379/11/project/output.json" (from /home/runner/work/pnpm/pnpm/exec/commands/test/exec.e2e.ts): use `with { type: 'json' }` for static imports, or pass `{ with: { type: 'json' } }` as the second argument to dynamic `import()`.
1237:  > project-1@1.0.0 build
...

1254:  \   ^__^
1255:  \  (oo)\_______
1256:  (__)\       )\/\
1257:  ||----w |
1258:  ||     ||
1259:  > project-2@1.0.0 build
1260:  > exit 1
1261:  > project-1@1.0.0 build
1262:  > node -e "setTimeout(() => console.log('project-1'), 1000)"
1263:  > project-3@1.0.0 build
1264:  > node -e "setTimeout(() => console.log('project-3'), 1000)"
1265:  > project-4@1.0.0 build
1266:  > exit 1
1267:  project-1
1268:  project-3
1269:  Jest: importing JSON without an import attribute is deprecated and will be a hard error in the next major. Update the import of "/home/runner/work/pnpm_tmp/27_6379/17/pnpm-exec-summary.json" (from /home/runner/work/pnpm/pnpm/exec/commands/test/exec.e2e.ts): use `with { type: 'json' }` for static imports, or pass `{ with: { type: 'json' } }` as the second argument to dynamic `import()`.
1270:  > project-1@1.0.0 build
1271:  > node -e "setTimeout(() => console.log('project-1'), 1000)"
1272:  > project-3@1.0.0 build
1273:  > node -e "setTimeout(() => console.log('project-3'), 1000)"
1274:  > project-2@1.0.0 build
1275:  > exit 1
1276:  Jest: importing JSON without an import attribute is deprecated and will be a hard error in the next major. Update the import of "/home/runner/work/pnpm_tmp/27_6379/18/pnpm-exec-summary.json" (from /home/runner/work/pnpm/pnpm/exec/commands/test/exec.e2e.ts): use `with { type: 'json' }` for static imports, or pass `{ with: { type: 'json' } }` as the second argument to dynamic `import()`.
1277:  > project-4@1.0.0 build
1278:  > exit 1
1279:  PASS test/exec.e2e.ts (11.748 s)
1280:  project-1
1281:  project-3
1282:  $ exit 0
1283:  $ exit 1
1284:  $ node recordArgs && exit 1
1285:  Jest: importing JSON without an import attribute is deprecated and will be a hard error in the next major. Update the import of "/home/runner/work/pnpm_tmp/28_6379/3/project/args.json" (from /home/runner/work/pnpm/pnpm/exec/commands/test/index.ts): use `with { type: 'json' }` for static imports, or pass `{ with: { type: 'json' } }` as the second argument to dynamic `import()`.
1286:  $ node recordArgs arg --flag=true --help -h
1287:  Jest: importing JSON without an import attribute is deprecated and will be a hard error in the next major. Update the import of "/home/runner/work/pnpm_tmp/28_6379/4/project/args.json" (from /home/runner/work/pnpm/pnpm/exec/commands/test/index.ts): use `with { type: 'json' }` for static imports, or pass `{ with: { type: 'json' } }` as the second argument to dynamic `import()`.
1288:  $ node recordArgs arg --flag=true --help -h
1289:  Jest: importing JSON without an import attribute is deprecated and will be a hard error in the next major. Update the import of "/home/runner/work/pnpm_tmp/28_6379/5/project/args.json" (from /home/runner/work/pnpm/pnpm/exec/commands/test/index.ts): use `with { type: 'json' }` for static imports, or pass `{ with: { type: 'json' } }` as the second argument to dynamic `import()`.
1290:  $ node recordArgs arg --flag=true --help -h
1291:  Jest: importing JSON without an import attribute is deprecated and will be a hard error in the next major. Update the import of "/home/runner/work/pnpm_tmp/28_6379/6/project/args.json" (from /home/runner/work/pnpm/pnpm/exec/commands/test/index.ts): use `with { type: 'json' }` for static imports, or pass `{ with: { type: 'json' } }` as the second argument to dynamic `import()`.
1292:  $ node recordArgs arg --flag=true --help -h
1293:  Jest: importing JSON without an import attribute is deprecated and will be a hard error in the next major. Update the import of "/home/runner/work/pnpm_tmp/28_6379/7/project/args.json" (from /home/runner/work/pnpm/pnpm/exec/commands/test/index.ts): use `with { type: 'json' }` for static imports, or pass `{ with: { type: 'json' } }` as the second argument to dynamic `import()`.
1294:  $ node recordArgs arg --flag=true --help -h
1295:  Jest: importing JSON without an import attribute is deprecated and will be a hard error in the next major. Update the import of "/home/runner/work/pnpm_tmp/28_6379/8/project/args.json" (from /home/runner/work/pnpm/pnpm/exec/commands/test/index.ts): use `with { type: 'json' }` for static imports, or pass `{ with: { type: 'json' } }` as the second argument to dynamic `import()`.
1296:  $ node "/tmp/pnpm-test-ipc-NOmF80/line.cjs" "/tmp/2e016de4-2f9a-41d9-836c-cb87d1a734fe.sock" "stop"
1297:  $ node "/tmp/pnpm-test-ipc-NOmF80/line.cjs" "/tmp/2e016de4-2f9a-41d9-836c-cb87d1a734fe.sock" "restart"
1298:  $ node "/tmp/pnpm-test-ipc-NOmF80/line.cjs" "/tmp/2e016de4-2f9a-41d9-836c-cb87d1a734fe.sock" "start"
1299:  $ node "/tmp/pnpm-test-ipc-yqnv16/line.cjs" "/tmp/0e31b190-b67c-4bdf-bd93-38328b9af7da.sock" "prestop"
1300:  $ node "/tmp/pnpm-test-ipc-yqnv16/line.cjs" "/tmp/0e31b190-b67c-4bdf-bd93-38328b9af7da.sock" "stop" && pnpm poststop
1301:  Already up to date
1302:  Done in 381ms using pnpm v11.5.1
1303:  $ node "/tmp/pnpm-test-ipc-yqnv16/line.cjs" "/tmp/0e31b190-b67c-4bdf-bd93-38328b9af7da.sock" "poststop"
1304:  $ node "/tmp/pnpm-test-ipc-yqnv16/line.cjs" "/tmp/0e31b190-b67c-4bdf-bd93-38328b9af7da.sock" "prerestart"
1305:  $ node "/tmp/pnpm-test-ipc-yqnv16/line.cjs" "/tmp/0e31b190-b67c-4bdf-bd93-38328b9af7da.sock" "restart"
1306:  $ node "/tmp/pnpm-test-ipc-yqnv16/line.cjs" "/tmp/0e31b190-b67c-4bdf-bd93-38328b9af7da.sock" "postrestart"
1307:  $ node "/tmp/pnpm-test-ipc-yqnv16/line.cjs" "/tmp/0e31b190-b67c-4bdf-bd93-38328b9af7da.sock" "prestart"
1308:  $ node "/tmp/pnpm-test-ipc-yqnv16/line.cjs" "/tmp/0e31b190-b67c-4bdf-bd93-38328b9af7da.sock" "start"
1309:  $ node "/tmp/pnpm-test-ipc-yqnv16/line.cjs" "/tmp/0e31b190-b67c-4bdf-bd93-38328b9af7da.sock" "poststart"
1310:  $ hello-world-js-bin > ./output.txt
1311:  $ foo bar
1312:  Jest: importing JSON without an import attribute is deprecated and will be a hard error in the next major. Update the import of "/home/runner/work/pnpm_tmp/28_6379/16/project/shell-input.json" (from /home/runner/work/pnpm/pnpm/exec/commands/test/index.ts): use `with { type: 'json' }` for static imports, or pass `{ with: { type: 'json' } }` as the second argument to dynamic `import()`.
1313:  $ node -e "let i = 2;setInterval(() => {if (!i--) process.exit(0); console.log(Date.now()); },16)" | node "/tmp/pnpm-test-ipc-830FAl/stdin.cjs" "/tmp/40a08919-fc65-444e-988c-7e2b9b3fdb75.sock"
1314:  $ node -e "let i = 2;setInterval(() => {if (!i--) process.exit(0); console.log(Date.now()); },16)" | node "/tmp/pnpm-test-ipc-acAv88/stdin.cjs" "/tmp/407f6b8b-27b7-46a2-a412-c14bf561eed8.sock"
1315:  $ node -e "if (process.env.NODE_OPTIONS !== '--max-old-space-size=1200') { process.exit(1) }"
1316:  $ node -e "assert.equal(process.version, 'v26.0.0')"
1317:  PASS test/index.ts (5.539 s)
1318:  touch: illegal option -- --bad-option
1319:  usage:
1320:  touch [-acfm] [-r file] [-t [[CC]YY]MMDDhhmm[.SS]] file ...
1321:  hello world
1322:  mkdir: path already exists: /home/runner/work/pnpm_tmp/29_6379/14/project/not-a-dir
1323:  Hello world!
1324:  Hello world!
1325:  Hello world!
1326:  Hello world!
1327:  Error: Missing ShellJS command name
1328:  shx: A wrapper for shelljs UNIX commands.
...

1343:  * ls
1344:  * find
1345:  * grep
1346:  * head
1347:  * ln
1348:  * mkdir
1349:  * rm
1350:  * mv
1351:  * sed
1352:  * sort
1353:  * tail
1354:  * test
1355:  * touch
1356:  * uniq
1357:  * which
1358:  * error
1359:  * help
...

1485:  ----------|---------|----------|---------|---------|-------------------
1486:  File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
1487:  ----------|---------|----------|---------|---------|-------------------
1488:  All files |   55.55 |        0 |      50 |   55.55 |                   
1489:  index.js |   55.55 |        0 |      50 |   55.55 | 46-63             
1490:  ----------|---------|----------|---------|---------|-------------------
1491:  Test Suites: 1 passed, 1 total
1492:  Tests:       2 passed, 2 total
1493:  Snapshots:   0 total
1494:  Time:        1.716 s
1495:  Ran all test suites.
1496:  ##[endgroup]
1497:  ##[group]@pnpm/installing.commands@1100.7.1 : .test installing/commands
1498:  $ cross-env NODE_OPTIONS="$NODE_OPTIONS --experimental-vm-modules --disable-warning=ExperimentalWarning --disable-warning=DEP0169" jest
1499:  �[2m2026-06-02T08:09:51.336420Z�[0m �[32m INFO�[0m �[2mpnpm_registry::server�[0m�[2m:�[0m pnpm-registry listening �[3mlisten�[0m�[2m=�[0m127.0.0.1:37561
1500:  Jest: importing JSON without an import attribute is deprecated and will be a hard error in the next major. Update the import of "/home/runner/work/pnpm_tmp/37_8966/11/project-1/package.json" (from /home/runner/work/pnpm/pnpm/installing/commands/test/miscRecursive.ts): use `with { type: 'json' }` for static imports, or pass `{ with: { type: 'json' } }` as the second argument to dynamic `import()`.
1501:  Jest: importing JSON without an import attribute is deprecated and will be a hard error in the next major. Update the import of "/home/runner/work/pnpm_tmp/37_8966/11/project-2/package.json" (from /home/runner/work/pnpm/pnpm/installing/commands/test/miscRecursive.ts): use `with { type: 'json' }` for static imports, or pass `{ with: { type: 'json' } }` as the second argument to dynamic `import()`.
1502:  Jest: importing JSON without an import attribute is deprecated and will be a hard error in the next major. Update the import of "/home/runner/work/pnpm_tmp/37_8966/12/project-1/package.json" (from /home/runner/work/pnpm/pnpm/installing/commands/test/miscRecursive.ts): use `with { type: 'json' }` for static imports, or pass `{ with: { type: 'json' } }` as the second argument to dynamic `import()`.
1503:  Jest: importing JSON without an import attribute is deprecated and will be a hard error in the next major. Update the import of "/home/runner/work/pnpm_tmp/37_8966/12/project-2/package.json" (from /home/runner/work/pnpm/pnpm/installing/commands/test/miscRecursive.ts): use `with { type: 'json' }` for static imports, or pass `{ with: { type: 'json' } }` as the second argument to dynamic `import()`.
1504:  PASS test/miscRecursive.ts (6.069 s)
1505:  PASS test/update/update.ts
1506:  PASS test/update/recursive.ts
1507:  Jest: importing JSON without an import attribute is deprecated and will be a hard error in the next major. Update the import of "/home/runner/work/pnpm_tmp/40_8966/1/project-1/package.json" (from /home/runner/work/pnpm/pnpm/installing/commands/test/add.ts): use `with { type: 'json' }` for static imports, or pass `{ with: { type: 'json' } }` as the second argument to dynamic `import()`.
1508:  Jest: importing JSON without an import attribute is deprecated and will be a hard error in the next major. Update the import of "/home/runner/work/pnpm_tmp/40_8966/2/project-1/package.json" (from /home/runner/work/pnpm/pnpm/installing/commands/test/add.ts): use `with { type: 'json' }` for static imports, or pass `{ with: { type: 'json' } }` as the second argument to dynamic `import()`.
1509:  Jest: importing JSON without an import attribute is deprecated and will be a hard error in the next major. Update the import of "/home/runner/work/pnpm_tmp/40_8966/3/project-1/package.json" (from /home/runner/work/pnpm/pnpm/installing/commands/test/add.ts): use `with { type: 'json' }` for static imports, or pass `{ with: { type: 'json' } }` as the second argument to dynamic `import()`.
1510:  Jest: importing JSON without an import attribute is deprecated and will be a hard error in the next major. Update the import of "/home/runner/work/pnpm_tmp/40_8966/5/project-1/package.json" (from /home/runner/work/pnpm/pnpm/installing/commands/test/add.ts): use `with { type: 'json' }` for static imports, or pass `{ with: { type: 'json' } }` as the second argument to dynamic `import()`.
1511:  Jest: importing JSON without an import attribute is deprecated and will be a hard error in the next major. Update the import of "/home/runner/work/pnpm_tmp/40_8966/8/project/package.json" (from /home/runner/work/pnpm/pnpm/installing/commands/test/add.ts): use `with { type: 'json' }` for static imports, or pass `{ with: { type: 'json' } }` as the second argument to dynamic `import()`.
1512:  PASS test/add.ts
1513:  PASS test/fetch.ts
1514:  PASS test/patchedDependencies.ts
1515:  PASS test/update/interactive.ts
1516:  PASS test/link.ts
1517:  PASS test/dedupe.ts
1518:  PASS test/saveCatalog.ts
1519:  PASS test/policyHandlers.ts
1520:  PASS test/update/getUpdateChoices.test.ts
1521:  PASS test/addJsr.ts
1522:  PASS test/install.ts
1523:  PASS test/remove/remove.ts
1524:  PASS test/import.ts
1525:  Jest: importing JSON without an import attribute is deprecated and will be a hard error in the next major. Update the import of "/home/runner/work/pnpm_tmp/51_8966/1/project-1/package.json" (from /home/runner/work/pnpm/pnpm/installing/commands/test/remove/workspace.ts): use `with { type: 'json' }` for static imports, or pass `{ with: { type: 'json' } }` as the second argument to dynamic `import()`.
1526:  Jest: importing JSON without an import attribute is deprecated and will be a hard error in the next major. Update the import of "/home/runner/work/pnpm_tmp/51_8966/1/project-2/package.json" (from /home/runner/work/pnpm/pnpm/installing/commands/test/remove/workspace.ts): use `with { type: 'json' }` for static imports, or pass `{ with: { type: 'json' } }` as the second argument to dynamic `import()`.
1527:  Jest: importing JSON without an import attribute is deprecated and will be a hard error in the next major. Update the import of "/home/runner/work/pnpm_tmp/51_8966/2/project-1/package.json" (from /home/runner/work/pnpm/pnpm/installing/commands/test/remove/workspace.ts): use `with { type: 'json' }` for static imports, or pass `{ with: { type: 'json' } }` as the second argument to dynamic `import()`.
1528:  Jest: importing JSON without an import attribute is deprecated and will be a hard error in the next major. Update the import of "/home/runner/work/pnpm_tmp/51_8966/2/project-2/package.json" (from /home/runner/work/pnpm/pnpm/installing/commands/test/remove/workspace.ts): use `with { type: 'json' }` for static imports, or pass `{ with: { type: 'json' } }` as the second argument to dynamic `import()`.
1529:  PASS test/remove/workspace.ts
1530:  Jest: importing JSON without an import attribute is deprecated and will be a hard error in the next major. Update the import of "/home/runner/work/pnpm_tmp/52_8966/1/project-1/package.json" (from /home/runner/work/pnpm/pnpm/installing/commands/test/addRecursive.ts): use `with { type: 'json' }` for static imports, or pass `{ with: { type: 'json' } }` as the second argument to dynamic `import()`.
1531:  Jest: importing JSON without an import attribute is deprecated and will be a hard error in the next major. Update the import of "/home/runner/work/pnpm_tmp/52_8966/1/project-2/package.json" (from /home/runner/work/pnpm/pnpm/installing/commands/test/addRecursive.ts): use `with { type: 'json' }` for static imports, or pass `{ with: { type: 'json' } }` as the second argument to dynamic `import()`.
1532:  Jest: importing JSON without an import attribute is deprecated and will be a hard error in the next major. Update the import of "/home/runner/work/pnpm_tmp/52_8966/2/project-1/package.json" (from /home/runner/work/pnpm/pnpm/installing/commands/test/addRecursive.ts): use `with { type: 'json' }` for static imports, or pass `{ with: { type: 'json' } }` as the second argument to dynamic `import()`.
1533:  Jest: importing JSON without an import attribute is deprecated and will be a hard error in the next major. Update the import of "/home/runner/work/pnpm_tmp/52_8966/2/project-2/package.json" (from /home/runner/work/pnpm/pnpm/installing/commands/test/addRecursive.ts): use `with { type: 'json' }` for static imports, or pass `{ with: { type: 'json' } }` as the second argument to dynamic `import()`.
1534:  PASS test/addRecursive.ts
...

1653:  $ node -e "console.log('preinstall-' + process.cwd())" | node "/tmp/pnpm-test-ipc-EpS1SS/stdin.cjs" "/tmp/98633b41-85cb-48c8-a779-3c116bd5c42a.sock"
1654:  $ node -e "console.log('install-' + process.cwd())" | node "/tmp/pnpm-test-ipc-EpS1SS/stdin.cjs" "/tmp/98633b41-85cb-48c8-a779-3c116bd5c42a.sock"
1655:  $ node -e "console.log('postinstall-' + process.cwd())" | node "/tmp/pnpm-test-ipc-EpS1SS/stdin.cjs" "/tmp/98633b41-85cb-48c8-a779-3c116bd5c42a.sock"
1656:  $ node -e "console.log('preprepare-' + process.cwd())" | node "/tmp/pnpm-test-ipc-EpS1SS/stdin.cjs" "/tmp/98633b41-85cb-48c8-a779-3c116bd5c42a.sock"
1657:  $ node -e "console.log('postprepare-' + process.cwd())" | node "/tmp/pnpm-test-ipc-EpS1SS/stdin.cjs" "/tmp/98633b41-85cb-48c8-a779-3c116bd5c42a.sock"
1658:  $ node -e "console.log('preinstall-' + process.cwd())" | node "/tmp/pnpm-test-ipc-pQyfwd/stdin.cjs" "/tmp/ff80982e-8655-4a2b-83a5-e91945758c19.sock"
1659:  $ node -e "console.log('install-' + process.cwd())" | node "/tmp/pnpm-test-ipc-pQyfwd/stdin.cjs" "/tmp/ff80982e-8655-4a2b-83a5-e91945758c19.sock"
1660:  $ node -e "console.log('postinstall-' + process.cwd())" | node "/tmp/pnpm-test-ipc-pQyfwd/stdin.cjs" "/tmp/ff80982e-8655-4a2b-83a5-e91945758c19.sock"
1661:  $ exit 1
1662:  $ node -e "fs.writeFileSync('output.json', JSON.stringify(process.env.INIT_CWD))"
1663:  PASS test/install/lifecycleScripts.ts (11.791 s)
1664:  PASS test/install/globalVirtualStore.ts
1665:  PASS test/install/autoInstallPeers.ts (31.845 s)
1666:  PASS test/install/optionalDependencies.ts (18.924 s)
1667:  PASS test/install/patch.ts
1668:  Jest: importing JSON without an import attribute is deprecated and will be a hard error in the next major. Update the import of "/home/runner/work/pnpm_tmp/73_9125/10/project/node_modules/.pnpm/tar-pkg-with-dep@file+..+tar.tgz/node_modules/tar-pkg-with-dep/package.json" (from /home/runner/work/pnpm/pnpm/installing/deps-installer/test/install/local.ts): use `with { type: 'json' }` for static imports, or pass `{ with: { type: 'json' } }` as the second argument to dynamic `import()`.
1669:  PASS test/install/local.ts
...

1687:  PASS test/install/updatingPkgJson.ts
1688:  PASS test/install/checkCustomResolverForceResolve.ts
1689:  PASS test/install/recordLockfileVerified.ts
1690:  PASS test/install/cyclicPeerDeterminism.ts
1691:  PASS test/install/denoRuntime.ts (18.082 s)
1692:  PASS test/install/dedupe.ts
1693:  PASS test/install/bunRuntime.ts (16.806 s)
1694:  PASS test/install/gitBranchLockfile.test.ts
1695:  PASS test/install/auth.ts
1696:  PASS test/install/packageExtensions.ts
1697:  PASS test/install/dedupeDirectDeps.ts
1698:  PASS test/filterPeerDependencyIssues.test.ts
1699:  PASS test/breakingChanges.ts
1700:  ● Console
1701:  console.log
1702:  this test will always fail on CI servers
1703:  at Object.<anonymous> (test/breakingChanges.ts:140:13)
1704:  PASS test/brokenLockfileIntegrity.ts
1705:  PASS test/install/only.ts
1706:  PASS test/install/aliases.ts
1707:  PASS test/install/hooks.ts
1708:  PASS test/install/prereleaseWeights.ts
1709:  PASS test/install/dedupeInWorkspace.ts
1710:  PASS test/install/bundledDependencies.ts
1711:  PASS test/install/customResolvers.ts
1712:  PASS test/install/resolutionMode.ts
1713:  PASS test/link.ts
1714:  PASS test/prune.ts
1715:  PASS test/install/injectWorkspacePackagesPersistence.test.ts
1716:  PASS test/install/errors.ts
1717:  PASS test/hoistedNodeLinker/uninstall.ts
...

1754:  parseWantedDependencies.js              |     100 |      100 |     100 |     100 |                                                                                                                                                                                                       
1755:  pnpmPkgJson.js                          |   66.66 |      100 |     100 |   66.66 | 8                                                                                                                                                                                                     
1756:  lib/install                              |   66.89 |    65.48 |   74.59 |    66.6 |                                                                                                                                                                                                       
1757:  checkCustomResolverForceResolve.js      |       0 |        0 |       0 |       0 | 9-42                                                                                                                                                                                                  
1758:  extendInstallOptions.js                 |   92.85 |     92.5 |     100 |   92.85 | 148,153                                                                                                                                                                                               
1759:  index.js                                |   66.88 |    64.82 |    69.6 |   66.38 | 61,110,128-130,220-222,242,250,258,279,282,284-285,336-339,456-487,500,649,676,700,711-724,800,828,948,1070,1090-1094,1104,1207-1210,1217,1361,1389,1430-1431,1529-1533,1542-1549,1561-1562,1598-1969 
1760:  link.js                                 |   98.52 |    93.79 |     100 |   98.44 | 232,361                                                                                                                                                                                               
1761:  recordLockfileVerified.js               |    62.5 |       50 |     100 |    62.5 | 13,15,17                                                                                                                                                                                              
1762:  reportPeerDependencyIssues.js           |   16.92 |    17.64 |   33.33 |   16.66 | 22-107                                                                                                                                                                                                
1763:  validateModules.js                      |   77.04 |    73.13 |     100 |   76.27 | 18,25-29,91-106,122,134                                                                                                                                                                               
1764:  verifyLockfileResolutions.js            |   84.28 |    59.52 |   83.33 |   83.58 | 69,81-88,143,173-177,195                                                                                                                                                                              
1765:  verifyLockfileResolutionsCache.js       |   50.83 |    45.07 |   83.33 |   51.69 | 65,77,112-114,152-153,157,162,180-181,186-200,229,235-236,259-260,276-279,284-327                                                                                                                     
1766:  writeLockfilesAndRecordVerified.js      |     100 |     87.5 |     100 |     100 | 6                                                                                                                                                                                                     
1767:  writeWantedLockfileAndRecordVerified.js |   83.33 |     62.5 |     100 |   83.33 | 19                                                                                                                                                                                                    
1768:  lib/install/checkCompatibility           |     100 |      100 |     100 |     100 |                                                                                                                                                                                                       
1769:  BreakingChangeError.js                  |     100 |      100 |     100 |     100 |                                                                                                                                                                                                       
1770:  CatalogVersionMismatchError.js          |     100 |      100 |     100 |     100 |                                                                                                                                                                                                       
1771:  ModulesBreakingChangeError.js           |     100 |      100 |     100 |     100 |                                                                                                                                                                                                       
1772:  UnexpectedStoreError.js                 |     100 |      100 |     100 |     100 |                                                                                                                                                                                                       
1773:  UnexpectedVirtualStoreDirError.js       |     100 |      100 |     100 |     100 |                                                                                                                                                                                                       
1774:  index.js                                |     100 |      100 |     100 |     100 |                                                                                                                                                                                                       
...

1936:  Force exiting Jest: Have you considered using `--detectOpenHandles` to detect async operations that kept running after all tests finished?
1937:  ##[endgroup]
1938:  ##[group]@pnpm/installing.package-requester@1101.0.10 : .test installing/package-requester
1939:  $ cross-env NODE_OPTIONS="$NODE_OPTIONS --experimental-vm-modules --disable-warning=ExperimentalWarning --disable-warning=DEP0169" jest
1940:  �[2m2026-06-02T08:17:38.899985Z�[0m �[32m INFO�[0m �[2mpnpm_registry::server�[0m�[2m:�[0m pnpm-registry listening �[3mlisten�[0m�[2m=�[0m127.0.0.1:44947
1941:  PASS test/index.ts
1942:  ✓ request package (326 ms)
1943:  ✓ request package but skip fetching (2 ms)
1944:  ✓ request package but skip fetching, when resolution is already available (4 ms)
1945:  ✓ refetch local tarball if its integrity has changed (83 ms)
1946:  ✓ refetch local tarball if its integrity has changed. The requester does not know the correct integrity (87 ms)
1947:  ✓ force fetch when resolution integrity differs from current package integrity (5 ms)
1948:  ✓ integrity of a tarball dependency is preserved when the resolver returns no integrity (1 ms)
1949:  ✓ fetchPackageToStore() (12 ms)
1950:  ✓ fetchPackageToStore() concurrency check (7 ms)
1951:  ✓ fetchPackageToStore() does not cache errors (19 ms)
1952:  ✓ always return a package manifest in the response (9 ms)
1953:  ✓ fetchPackageToStore() fetch raw manifest of cached package (6 ms)
1954:  ✓ refetch package to store if it has been modified (375 ms)
1955:  ✓ do not fetch an optional package that is not installable (5 ms)
1956:  ✓ fetch a git package without a package.json (1178 ms)
1957:  ✓ throw exception if the package data in the store differs from the expected data (28 ms)
1958:  ✓ don't throw an error if the package was updated, so the expectedPkg has a different version than the version in the store (47 ms)
1959:  ✓ the version in the bundled manifest should be normalized (91 ms)
...

2023:  src                           |     100 |      100 |     100 |     100 |                   
2024:  getEditDirPath.ts            |     100 |      100 |     100 |     100 |                   
2025:  test/utils                    |     100 |      100 |     100 |     100 |                   
2026:  index.ts                     |     100 |      100 |     100 |     100 |                   
2027:  -------------------------------|---------|----------|---------|---------|-------------------
2028:  Test Suites: 2 passed, 2 total
2029:  Tests:       39 passed, 39 total
2030:  Snapshots:   0 total
2031:  Time:        10.732 s
2032:  Ran all test suites.
2033:  Force exiting Jest: Have you considered using `--detectOpenHandles` to detect async operations that kept running after all tests finished?
2034:  ##[endgroup]
2035:  ##[group]pnpm@11.5.1 : .test pnpm
2036:  $ cross-env NODE_OPTIONS="$NODE_OPTIONS --experimental-vm-modules --disable-warning=ExperimentalWarning --disable-warning=DEP0169" jest
2037:  �[2m2026-06-02T08:17:58.041869Z�[0m �[32m INFO�[0m �[2mpnpm_registry::server�[0m�[2m:�[0m pnpm-registry listening �[3mlisten�[0m�[2m=�[0m127.0.0.1:36053
2038:  Jest: importing JSON without an import attribute is deprecated and will be a hard error in the next major. Update the import of "/home/runner/work/pnpm_tmp/135_11792/11/project-1/package.json" (from /home/runner/work/pnpm/pnpm/pnpm/test/monorepo/index.ts): use `with { type: 'json' }` for static imports, or pass `{ with: { type: 'json' } }` as the second argument to dynamic `import()`.
2039:  Jest: importing JSON without an import attribute is deprecated and will be a hard error in the next major. Update the import of "/home/runner/work/pnpm_tmp/135_11792/12/project-1/package.json" (from /home/runner/work/pnpm/pnpm/pnpm/test/monorepo/index.ts): use `with { type: 'json' }` for static imports, or pass `{ with: { type: 'json' } }` as the second argument to dynamic `import()`.
2040:  PASS test/monorepo/index.ts (51.117 s)
2041:  PASS test/verifyDepsBeforeRun/multiProjectWorkspace.ts (40.337 s)
2042:  Jest: importing JSON without an import attribute is deprecated and will be a hard error in the next major. Update the import of "/home/runner/work/pnpm_tmp/137_11792/4/global/pnpm/global/v11/store/v11/links/@pnpm.e2e/peer-c/2.0.0/d71da6b1f0c03c43b3407dec8e59e89884499d011372e2c6d5105461c32163ca/node_modules/@pnpm.e2e/peer-c/package.json" (from /home/runner/work/pnpm/pnpm/pnpm/test/install/global.ts): use `with { type: 'json' }` for static imports, or pass `{ with: { type: 'json' } }` as the second argument to dynamic `import()`.
2043:  PASS test/install/global.ts (25.922 s)
2044:  PASS test/update.ts (29.26 s)
2045:  FAIL test/packageManagerCheck.test.ts (20.435 s)
2046:  ● release-brittle: may fail until current version is published to npm › pnpm --version exits promptly when devEngines.packageManager matches the running pnpm
2047:  expect(received).toBe(expected) // Object.is equality
2048:  Expected: 0
2049:  Received: 1
2050:  �[0m �[90m 714 |�[39m     �[36mconst�[39m { status�[33m,�[39m stdout } �[33m=�[39m execPnpmSync([�[32m'--version'�[39m]�[33m,�[39m { timeout�[33m:�[39m �[35m30�[39m_000 })
2051:  �[90m 715 |�[39m
2052:  �[31m�[1m>�[22m�[39m�[90m 716 |�[39m     expect(status)�[33m.�[39mtoBe(�[35m0�[39m)
2053:  �[90m     |�[39m                    �[31m�[1m^�[22m�[39m
2054:  �[90m 717 |�[39m     expect(stdout�[33m.�[39mtoString()�[33m.�[39mtrim())�[33m.�[39mtoBe(pnpmVersion)
2055:  �[90m 718 |�[39m   })
2056:  �[90m 719 |�[39m�[0m
2057:  at Object.<anonymous> (test/packageManagerCheck.test.ts:716:20)
2058:  ● release-brittle: may fail until current version is published to npm › devEngines.packageManager with version range should match current version
2059:  expect(received).toBe(expected) // Object.is equality
2060:  Expected: 0
2061:  Received: 1
2062:  �[0m �[90m 731 |�[39m     �[36mconst�[39m { status } �[33m=�[39m execPnpmSync([�[32m'install'�[39m])
2063:  �[90m 732 |�[39m
2064:  �[31m�[1m>�[22m�[39m�[90m 733 |�[39m     expect(status)�[33m.�[39mtoBe(�[35m0�[39m)
2065:  �[90m     |�[39m                    �[31m�[1m^�[22m�[39m
2066:  �[90m 734 |�[39m   })
2067:  �[90m 735 |�[39m })
2068:  �[90m 736 |�[39m�[0m
2069:  at Object.<anonymous> (test/packageManagerCheck.test.ts:733:20)
2070:  Jest: importing JSON without an import attribute is deprecated and will be a hard error in the next major. Update the import of "/home/runner/work/pnpm_tmp/140_11792/15/project/package.json" (from /home/runner/work/pnpm/pnpm/pnpm/test/install/misc.ts): use `with { type: 'json' }` for static imports, or pass `{ with: { type: 'json' } }` as the second argument to dynamic `import()`.
2071:  PASS test/install/misc.ts (46.262 s)
2072:  PASS test/saveCatalog.ts (8.951 s)
2073:  PASS test/dlx.ts (42.007 s)
2074:  Jest: importing JSON without an import attribute is deprecated and will be a hard error in the next major. Update the import of "/home/runner/work/pnpm_tmp/143_11792/19/project/package.json" (from /home/runner/work/pnpm/pnpm/pnpm/test/install/hooks.ts): use `with { type: 'json' }` for static imports, or pass `{ with: { type: 'json' } }` as the second argument to dynamic `import()`.
2075:  PASS test/install/hooks.ts (14.811 s)
2076:  PASS test/install/minimumReleaseAge.ts (13.816 s)
2077:  Jest: importing JSON without an import attribute is deprecated and will be a hard error in the next major. Update the import of "/home/runner/work/pnpm_tmp/145_11792/9/project/package.json" (from /home/runner/work/pnpm/pnpm/pnpm/test/install/lifecycleScripts.ts): use `with { type: 'json' }` for static imports, or pass `{ with: { type: 'json' } }` as the second argument to dynamic `import()`.
2078:  PASS test/install/lifecycleScripts.ts (22.236 s)
2079:  PASS artifacts/exe/test/setup.test.ts
2080:  PASS test/verifyDepsBeforeRun/exec.ts (16.684 s)
2081:  PASS test/recursive/misc.ts (8.652 s)
2082:  PASS test/hooks.ts (13.431 s)
2083:  PASS test/clean.ts (7.914 s)
2084:  FAIL test/configurationalDependencies.test.ts (8.942 s)
2085:  ● release-brittle: may fail until current version is published to npm › packageManagerDependencies is refreshed when pnpm is invoked via corepack (#11397)
2086:  Exit code 1
2087:  [ERROR] No matching version found for @pnpm/exe@11.5.1 while fetching it from http://localhost:36053/
2088:  For help, run: pnpm help install
2089:  �[0m �[90m 54 |�[39m         reject(�[36mnew�[39m �[33mError�[39m(�[32m`Killed by signal ${signal}\n\n${Buffer.concat(output).toString()}`�[39m))
2090:  �[90m 55 |�[39m       } �[36melse�[39m �[36mif�[39m (code) {
2091:  �[31m�[1m>�[22m�[39m�[90m 56 |�[39m         reject(�[36mnew�[39m �[33mError�[39m(�[32m`Exit code ${code}\n\n${Buffer.concat(output).toString()}`�[39m))
2092:  �[90m    |�[39m                �[31m�[1m^�[22m�[39m
...

2114:  at log (../agent/server/src/createRegistryServer.ts:413:13)
2115:  console.log
2116:  [SERVER /v1/install] 13ms digests=7
2117:  at log (../agent/server/src/createRegistryServer.ts:413:13)
2118:  console.log
2119:  [SERVER /v1/install] 12ms digests=4
2120:  at log (../agent/server/src/createRegistryServer.ts:413:13)
2121:  console.log
2122:  [SERVER /v1/install] 9ms digests=4
2123:  at log (../agent/server/src/createRegistryServer.ts:413:13)
2124:  console.log
2125:  [SERVER /v1/install] 9ms digests=0
2126:  at log (../agent/server/src/createRegistryServer.ts:413:13)
2127:  PASS test/config/get.ts (15.252 s)
2128:  PASS test/verifyDepsBeforeRun/singleProjectWorkspace.ts (12.446 s)
2129:  Jest: importing JSON without an import attribute is deprecated and will be a hard error in the next major. Update the import of "/home/runner/work/pnpm_tmp/155_11792/1/project/args.json" (from /home/runner/work/pnpm/pnpm/pnpm/test/run.ts): use `with { type: 'json' }` for static imports, or pass `{ with: { type: 'json' } }` as the second argument to dynamic `import()`.
2130:  Jest: importing JSON without an import attribute is deprecated and will be a hard error in the next major. Update the import of "/home/runner/work/pnpm_tmp/155_11792/2/project/args.json" (from /home/runner/work/pnpm/pnpm/pnpm/test/run.ts): use `with { type: 'json' }` for static imports, or pass `{ with: { type: 'json' } }` as the second argument to dynamic `import()`.
2131:  Jest: importing JSON without an import attribute is deprecated and will be a hard error in the next major. Update the import of "/home/runner/work/pnpm_tmp/155_11792/3/project/args.json" (from /home/runner/work/pnpm/pnpm/pnpm/test/run.ts): use `with { type: 'json' }` for static imports, or pass `{ with: { type: 'json' } }` as the second argument to dynamic `import()`.
2132:  PASS test/run.ts (15.354 s)
2133:  PASS test/getConfig.test.ts
2134:  ● Console
2135:  console.warn
2136:  [WARN] Failed to replace env in config: ${ENV_VAR_123}
2137:  �[0m �[90m 37 |�[39m
...

2146:  at Object.<anonymous> (test/getConfig.test.ts:57:3)
2147:  PASS test/switchingVersions.test.ts (13.211 s)
2148:  PASS test/monorepo/dedupePeers.test.ts (5.56 s)
2149:  PASS test/config/list.ts
2150:  PASS test/install/pacquet.ts
2151:  PASS src/syncEnvLockfile.test.ts
2152:  PASS test/cli.ts (12.7 s)
2153:  PASS test/install/globalVirtualStore.ts
2154:  PASS test/deploy.ts
2155:  PASS test/verifyDepsBeforeRun/install.ts (5.212 s)
2156:  PASS test/install/supportedArchitectures.ts
2157:  PASS test/install/runtimeOnFail.ts (9.858 s)
2158:  PASS test/recursive/filter.ts
2159:  PASS test/recursive/rebuild.ts
2160:  PASS test/withCommand.test.ts
2161:  PASS test/errorHandler.test.ts
2162:  PASS test/ci.ts
...

2171:  PASS test/install/configDeps.ts
2172:  PASS test/sbom.ts
2173:  PASS src/shouldPersistLockfile.test.ts
2174:  PASS test/uninstall.ts
2175:  PASS test/install/selfUpdate.ts
2176:  PASS test/install/optional.ts
2177:  PASS test/exec.ts
2178:  PASS test/install/yesFlag.ts
2179:  PASS test/install/only.ts
2180:  PASS test/verifyDepsBeforeRun/engineCheck.ts
2181:  PASS test/install/issue-8959.ts
2182:  PASS test/monorepo/peerDependencies.ts
2183:  PASS test/root.ts
2184:  PASS test/install/preferOffline.ts
2185:  PASS test/bin.ts
2186:  PASS test/formatError.test.ts
2187:  PASS test/install/nodeRuntime.ts
2188:  PASS test/config.ts
2189:  PASS test/help.spec.ts
2190:  ---------------------------|---------|----------|---------|---------|--------------------------------------
2191:  File                       | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s                    
2192:  ---------------------------|---------|----------|---------|---------|--------------------------------------
2193:  All files                  |   68.14 |     64.7 |      64 |   68.13 |                                      
2194:  artifacts/exe             |   85.71 |       90 |     100 |   85.71 |                                      
2195:  platform-pkg-name.js     |   85.71 |       90 |     100 |   85.71 | 16                                   
2196:  src                       |   76.69 |    64.36 |     100 |      75 |                                      
2197:  checkForUpdates.ts       |     100 |     87.5 |     100 |     100 | 40                                   
2198:  formatError.ts           |     100 |      100 |     100 |     100 |                                      
2199:  getConfig.ts             |   60.65 |    44.44 |     100 |   59.64 | 35,77-94,122-123,133-137,148,153-154 
2200:  shouldPersistLockfile.ts |     100 |      100 |     100 |     100 |                                      
2201:  syncEnvLockfile.ts       |     100 |      100 |     100 |     100 |                                      
2202:  src/cmd                   |   29.41 |    18.75 |   28.57 |   29.41 |                                      
2203:  help.ts                  |   29.41 |    18.75 |   28.57 |   29.41 | 15-25,47,49,72-383                   
2204:  test/utils                |   64.64 |    73.68 |   56.66 |   67.04 |                                      
2205:  distTags.ts              |     100 |      100 |     100 |     100 |                                      
2206:  execPnpm.ts              |   66.26 |    76.36 |   57.14 |   68.91 | 54,71-109,193,229-238                
2207:  isPortInUse.ts           |   66.66 |        0 |      80 |      70 | 7-10                                 
2208:  localPkg.ts              |       0 |      100 |       0 |       0 | 4-8                                  
2209:  testDefaults.ts          |       0 |      100 |       0 |       0 | 6                                    
2210:  ---------------------------|---------|----------|---------|---------|--------------------------------------
2211:  Summary of all failing tests
2212:  FAIL test/packageManagerCheck.test.ts (20.435 s)
2213:  ● release-brittle: may fail until current version is published to npm › pnpm --version exits promptly when devEngines.packageManager matches the running pnpm
2214:  expect(received).toBe(expected) // Object.is equality
2215:  Expected: 0
2216:  Received: 1
2217:  �[0m �[90m 714 |�[39m     �[36mconst�[39m { status�[33m,�[39m stdout } �[33m=�[39m execPnpmSync([�[32m'--version'�[39m]�[33m,�[39m { timeout�[33m:�[39m �[35m30�[39m_000 })
2218:  �[90m 715 |�[39m
2219:  �[31m�[1m>�[22m�[39m�[90m 716 |�[39m     expect(status)�[33m.�[39mtoBe(�[35m0�[39m)
2220:  �[90m     |�[39m                    �[31m�[1m^�[22m�[39m
2221:  �[90m 717 |�[39m     expect(stdout�[33m.�[39mtoString()�[33m.�[39mtrim())�[33m.�[39mtoBe(pnpmVersion)
2222:  �[90m 718 |�[39m   })
2223:  �[90m 719 |�[39m�[0m
2224:  at Object.<anonymous> (test/packageManagerCheck.test.ts:716:20)
2225:  ● release-brittle: may fail until current version is published to npm › devEngines.packageManager with version range should match current version
2226:  expect(received).toBe(expected) // Object.is equality
2227:  Expected: 0
2228:  Received: 1
2229:  �[0m �[90m 731 |�[39m     �[36mconst�[39m { status } �[33m=�[39m execPnpmSync([�[32m'install'�[39m])
2230:  �[90m 732 |�[39m
2231:  �[31m�[1m>�[22m�[39m�[90m 733 |�[39m     expect(status)�[33m.�[39mtoBe(�[35m0�[39m)
2232:  �[90m     |�[39m                    �[31m�[1m^�[22m�[39m
2233:  �[90m 734 |�[39m   })
2234:  �[90m 735 |�[39m })
2235:  �[90m 736 |�[39m�[0m
2236:  at Object.<anonymous> (test/packageManagerCheck.test.ts:733:20)
2237:  FAIL test/configurationalDependencies.test.ts (8.942 s)
2238:  ● release-brittle: may fail until current version is published to npm › packageManagerDependencies is refreshed when pnpm is invoked via corepack (#11397)
2239:  Exit code 1
2240:  [ERROR] No matching version found for @pnpm/exe@11.5.1 while fetching it from http://localhost:36053/
2241:  For help, run: pnpm help install
2242:  �[0m �[90m 54 |�[39m         reject(�[36mnew�[39m �[33mError�[39m(�[32m`Killed by signal ${signal}\n\n${Buffer.concat(output).toString()}`�[39m))
2243:  �[90m 55 |�[39m       } �[36melse�[39m �[36mif�[39m (code) {
2244:  �[31m�[1m>�[22m�[39m�[90m 56 |�[39m         reject(�[36mnew�[39m �[33mError�[39m(�[32m`Exit code ${code}\n\n${Buffer.concat(output).toString()}`�[39m))
2245:  �[90m    |�[39m                �[31m�[1m^�[22m�[39m
2246:  �[90m 57 |�[39m       } �[36melse�[39m {
2247:  �[90m 58 |�[39m         resolve()
2248:  �[90m 59 |�[39m       }�[0m
2249:  at ChildProcess.<anonymous> (test/utils/execPnpm.ts:56:16)
2250:  Test Suites: 2 failed, 2 skipped, 64 passed, 66 of 68 total
2251:  Tests:       3 failed, 14 skipped, 518 passed, 535 total
2252:  Snapshots:   0 total
2253:  Time:        550.643 s
2254:  Ran all test suites.
2255:  Force exiting Jest: Have you considered using `--detectOpenHandles` to detect async operations that kept running after all tests finished?
2256:  /home/runner/work/pnpm/pnpm/pnpm:
2257:  [ERR_PNPM_RECURSIVE_RUN_FIRST_FAIL] pnpm@11.5.1 .test: `cross-env NODE_OPTIONS="$NODE_OPTIONS --experimental-vm-modules --disable-warning=ExperimentalWarning --disable-warning=DEP0169" jest`
2258:  Exit status 1
2259:  ##[group]@pnpm/exe@11.5.1 : .test pnpm/artifacts/exe
2260:  $ cross-env NODE_OPTIONS="$NODE_OPTIONS --experimental-vm-modules --disable-warning=ExperimentalWarning --disable-warning=DEP0169" jest
2261:  [ELIFECYCLE] Command failed with exit code 1.
2262:  [ELIFECYCLE] Command failed with exit code 1.
2263:  PASS test/setup.test.ts
...

2272:  ✓ uses linuxstatic- prefix for linux + musl libc family (2 ms)
2273:  ✓ uses linux- prefix when libc is glibc or unknown (1 ms)
2274:  ✓ libc is irrelevant on non-linux platforms
2275:  ✓ normalizes ia32 to x86 on win32 only (1 ms)
2276:  ----------------------|---------|----------|---------|---------|-------------------
2277:  File                  | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
2278:  ----------------------|---------|----------|---------|---------|-------------------
2279:  All files             |   85.71 |       90 |     100 |   85.71 |                   
2280:  platform-pkg-name.js |   85.71 |       90 |     100 |   85.71 | 16                
2281:  ----------------------|---------|----------|---------|---------|-------------------
2282:  Test Suites: 1 passed, 1 total
2283:  Tests:       4 skipped, 7 passed, 11 total
2284:  Snapshots:   0 total
2285:  Time:        0.638 s
2286:  Ran all test suites.
2287:  ##[error]Process completed with exit code 1.
2288:  Post job cleanup.

@zkochan zkochan merged commit 722b9cd into main Jun 2, 2026
31 of 32 checks passed
@zkochan zkochan deleted the fix/skip-minimumReleaseAge-for-non-registry-tarball branch June 2, 2026 08:59
zkochan added a commit that referenced this pull request Jun 2, 2026
## What

The lockfile resolution verifier now confirms that a registry entry pinning an explicit `tarball` URL points at the artifact the registry's own metadata lists for that `name@version`. A mismatch — or any entry that can't be confirmed against the registry — is rejected with `ERR_PNPM_TARBALL_URL_MISMATCH`.

## Why

Follow-up to the design discussion on #12122. The verifier checked the age/trust of `name@version` against the registry packument but never bound the lockfile's `tarball` URL to it. For the non-standard entries pnpm preserves a tarball URL for (npm Enterprise, GitHub Packages — see `toLockfileResolution`), pnpm fetches straight from that URL. So a **tampered lockfile could pair a trusted `name@version` with an attacker-chosen tarball URL** (plus a matching integrity for the attacker's bytes); verification passed against the legitimate version while the install fetched the attacker's bytes. Defending a checked-in lockfile is explicitly in this feature's threat model.

## How

- For a registry-keyed entry that pins an explicit `tarball`, fetch the packument and assert the URL equals `versions[v].dist.tarball`. The comparison canonicalizes away benign differences — http/https scheme, default ports (`:443`/`:80`), and `%2f` scope-separator encoding (case-insensitive) — so only real mismatches are flagged. The packument is fetched from the user's configured registry (the lockfile's tarball host can't redirect it), and named-registry routing uses the same canonicalization so a scheme/`%2f`-only difference doesn't route to the wrong packument.
- **The binding is unconditional.** It runs regardless of `minimumReleaseAge`/`trustPolicy` and is **not** narrowed by their exclude lists, because it guards *integrity*, not *maturity/trust*. Disabling the age/trust policies must not silently disable anti-tamper. (`createNpmResolutionVerifier` therefore always returns a verifier.)
- **It is fail-closed.** An entry passes only when the registry metadata affirmatively lists the version with a matching tarball URL. If the metadata can't be fetched, doesn't list the version, or omits `dist.tarball`, the entry is rejected — otherwise a tampered lockfile could smuggle a malicious URL past the check by pointing it at a `name@version` the registry can't vouch for.
  - **Behavior change:** as a result, an install that re-verifies a lockfile (its content changed since the last verified run, so the verification cache no longer short-circuits) now requires the configured registry to be reachable. `trustLockfile` is the opt-out for environments that treat the on-disk lockfile as already trusted.
- **Verification cache.** The policy snapshot records a `tarballUrlBinding` marker and `canTrustPastCheck` requires it, so a cache record written before this rule existed is re-verified rather than trusted (closing an upgrade-time bypass).
- Entries with no explicit `tarball` reconstruct the URL from name+version+registry and are inherently bound (no check). `file:`/git-hosted resolutions stay out of scope (#12122).
- Threads `nonSemverVersion` to the verifier so URL-keyed tarball deps (a remote `https:` tarball that carries a semver `version` copied from its manifest) are recognized as deliberate non-registry deps and skipped — also fixing a latent release-age over-match on them. The candidate dedupe key includes `nonSemverVersion` so a registry snapshot and a URL-keyed snapshot sharing a `name@version` and serialized resolution stay distinct.

Mirrored in pacquet (`create_npm_resolution_verifier`). The dedupe-key change is TS-only: pacquet's candidate `version` comes from the lockfile key suffix, so the two shapes never share a key there.

## Tests

- TS: confirmed mismatch → violation; non-standard URL matching metadata → pass; default-port/scheme difference → pass; URL-keyed dep → skipped; URL binding runs (and fails closed) with no age/trust policy configured; `canTrustPastCheck` rejects a cache record lacking the binding marker. Regression-verified (the mismatch test fails when the check is disabled).
- pacquet: mirror tests + the no-policy / `minimumReleaseAge: 0` / `trustPolicy: off` cases, default-port/scheme equivalence, and the missing-`tarballUrlBinding` cache rejection. A few install-dispatch / resolution-reuse tests that pin a deliberately bogus tarball URL (or run against an unreachable registry to prove resolution reuse) now set `trustLockfile`, since the always-on fail-closed tarball-URL check would otherwise flag the fixture before the path under test runs.
- `clippy --deny warnings`, `fmt`, and `dylint` clean.
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.

file: dependencies are checked for minimumReleaseAge

4 participants