Skip to content

fix: fail by default when a tarball does not match the locked integrity#11968

Merged
zkochan merged 13 commits into
mainfrom
vuln7
May 27, 2026
Merged

fix: fail by default when a tarball does not match the locked integrity#11968
zkochan merged 13 commits into
mainfrom
vuln7

Conversation

@zkochan

@zkochan zkochan commented May 26, 2026

Copy link
Copy Markdown
Member

Summary

pnpm install (non-frozen) used to react to ERR_PNPM_TARBALL_INTEGRITY by logging the error, silently re-resolving from the registry, and overwriting the locked integrity. The lockfile's integrity was effectively advisory by default — a compromised registry, proxy, or republished version could substitute attacker-controlled content on a clean machine even though the project shipped a committed pnpm-lock.yaml.

Integrity mismatches against the lockfile now fail by default.

The only opt-in is pnpm install --update-checksums — a new flag, narrowly scoped to refreshing the locked integrity values. Mirrors yarn's flag of the same name. A warning still prints when the bypass takes effect so the rewrite stays auditable.

--force and pnpm update deliberately do not bypass the integrity check. They are routine refresh operations; silently overwriting a locked integrity in those flows would erase the protection a committed lockfile is supposed to provide. --frozen-lockfile behavior is unchanged. --fix-lockfile keeps its documented purpose (filling in missing lockfile entries) and is also not a bypass. Combining --frozen-lockfile with --update-checksums errors out — frozen mode refuses to rewrite the lockfile, which is exactly what --update-checksums is for.

--update-checksums also bypasses the resolver's on-disk metadata cache fast path (pickPackage.ts:271, pick_package.rs:531). Without that, a stale on-disk packument that already contained the pinned version would short-circuit the registry entirely and the flag would silently no-op on dev machines. With the gate, every first-encounter goes through a conditional GET; the in-memory cache is left alone so second-and-onward references within the same install still hit cached fresh data (one network round-trip per unique package, not per reference).

Reported by

Reported privately via the security channel. The reproduction:

  1. Publish example-package@1.0.0 with content v1 and install with pnpm; lockfile records the v1 integrity.
  2. Replace the registry's tarball+metadata for the same 1.0.0 with content v2.
  3. On a clean store/cache, run pnpm install. Before this fix, pnpm logged ERR_PNPM_TARBALL_INTEGRITY but exited 0 with v2 installed and the lockfile rewritten to the new integrity. After this fix, the same install exits non-zero.

Prior art

  • npm (sebhastian): hard-fails with EINTEGRITY. No dedicated override flag — recovery is npm cache clean --force, manually editing the lockfile, or deleting it.
  • yarn (Sean C Davis): hard-fails with "Integrity check failed". Has a dedicated yarn install --update-checksums flag — pnpm now adopts the same name.

Pacquet parity

Pacquet was already fail-hard on integrity mismatch by default (no auto-repair path to remove). This PR brings the rest of the surface into line so pnpm install --update-checksums keeps working when pacquet is the materialization target, and pacquet install --update-checksums behaves identically standalone:

  • New --update-checksums flag on pacquet install (crates/cli/src/cli_args/install.rs), plumbed through Install and InstallWithFreshLockfile into the resolver.
  • When the flag is set, pacquet skips the frozen-lockfile fast path and routes through the fresh-resolve path so locked integrity values get rewritten from the registry.
  • --frozen-lockfile + --update-checksums errors with pacquet_package_manager::frozen_lockfile_with_outdated_lockfile, mirroring pnpm's ERR_PNPM_FROZEN_LOCKFILE_WITH_OUTDATED_LOCKFILE.
  • pacquet_tarball::verify_checksum_error now carries a help hint pointing at --update-checksums and calling out the supply-chain implication, matching the updated pnpm TarballIntegrityError.
  • The disk fast-path gate is mirrored in crates/resolving-npm-resolver/src/pick_package.rs:531, with the flag threaded from ResolveOptionsPickPackageOptions.

Test plan

  • pnpm --filter @pnpm/installing.deps-installer test brokenLockfileIntegrity.ts — passes (covers plain install fails, --update-checksums recovers, --force still fails)
  • pnpm --filter @pnpm/installing.deps-installer test fixLockfile.ts — passes (--fix-lockfile semantics unchanged)
  • pnpm --filter @pnpm/worker test — passes
  • pnpm --filter @pnpm/tarball-fetcher test — passes
  • cargo nextest run -p pacquet-package-manager install::tests — 79 tests pass, new should_error_when_frozen_lockfile_and_update_checksums_are_both_set included
  • cargo nextest run -p pacquet-resolving-npm-resolver -p pacquet-resolving-deps-resolver -p pacquet-package-manager — 614 tests pass
  • cargo clippy --locked --workspace --all-targets -- --deny warnings — clean
  • CI green

Written by an agent (Claude Code, claude-opus-4-7).

@coderabbitai

coderabbitai Bot commented May 26, 2026

Copy link
Copy Markdown

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

pnpm now aborts installs when a tarball's integrity differs from the committed lockfile by default; locked integrity is only refreshed when the caller explicitly opts into repair via --update-checksums, --force, or pnpm update. Tests, CLI surface, installer gating logic, error hints, and the changeset were updated to reflect this behavior.

Changes

Integrity-Mismatch Hard Failure

Layer / File(s) Summary
Installer: opt-in gating and resolution changes
installing/deps-installer/src/install/index.ts
Treats opts.updateChecksums as a full-resolution trigger; rethrows integrity errors by default and enables integrity-refresh recovery when opted in.
CLI flag and options surface
installing/commands/src/install.ts, installing/deps-installer/src/install/extendInstallOptions.ts
Introduces --update-checksums CLI flag, adds updateChecksums?: boolean to InstallCommandOptions, and adds updateChecksums: boolean to StrictInstallOptions.
Tests: assert fail-by-default and opt-in recovery
installing/deps-installer/test/brokenLockfileIntegrity.ts
Tests now assert installations fail by default on lockfile checksum mismatches and verify recovery when updateChecksums is used; imports and assertions updated accordingly.
Worker & tarball diagnostics
worker/src/index.ts, pacquet/crates/tarball/src/lib.rs
Updates TarballIntegrityError hint and tarball checksum diagnostic help to instruct not to silently overwrite locked integrity and to list remediation commands (pnpm store prune, pnpm install --update-checksums, pnpm update/--force).
Pacquet: propagate flag and enforce guard
pacquet/crates/cli/src/cli_args/install.rs, pacquet/crates/package-manager/src/install.rs, pacquet/crates/package-manager/src/add.rs, pacquet/crates/package-manager/src/install/tests.rs
Adds update_checksums CLI flag and Install field, forwards the flag through Install dispatch, forces off the frozen/fast path when requested, and errors when combined with --frozen-lockfile; tests updated to include the new field and assert the guard.
Release notes
.changeset/integrity-mismatch-fails-by-default.md
Changeset documents the new default: tarball integrity mismatches fail by default and require explicit recovery opt-in (--update-checksums, --force, or pnpm update).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • pnpm/pnpm#11824: Modifies pacquet install dispatch logic related to frozen/fast-path decisions, related to take_frozen_path changes.
  • pnpm/pnpm#11904: Also adjusts frozen-install control flow in Install::run; closely related to the frozen-path logic updates here.
  • pnpm/pnpm#11481: Introduces tarball integrity recording for git-hosted tarballs; related to integrity-error handling and enforcement.

Poem

🐰 I nibble at a lockfile tight,

A checksum blinks; installs halt in sight.
Wave flags — update checksums, force the mend,
Or prune the store and then ascend.
Hop safe, dear devs, and guard each blend.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and accurately describes the main change: introducing a hard-fail default behavior for tarball integrity mismatches against the committed lockfile.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ 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 vuln7

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

Fail by default on tarball integrity mismatches against lockfile

🐞 Bug fix ✨ Enhancement

Grey Divider

Walkthroughs

Description
• Fail by default on tarball integrity mismatches against lockfile
  - Previously silently re-resolved and overwrote locked integrity
  - Now requires explicit opt-in via --fix-lockfile, --force, or pnpm update
• Add lockfileRepairOptedIn() function to gate integrity recovery
• Update error hint to explain supply-chain implications and recovery options
• Enhance test coverage for integrity mismatch scenarios
Diagram
flowchart LR
  A["Tarball Integrity Mismatch"] --> B{Repair Opted In?}
  B -->|No| C["Throw Error"]
  B -->|Yes| D["Re-resolve & Update Lockfile"]
  E["--fix-lockfile<br/>--force<br/>pnpm update"] --> B
  F["--frozen-lockfile"] --> C

Loading

Grey Divider

File Changes

1. installing/deps-installer/src/install/index.ts 🐞 Bug fix +17/-2

Gate integrity recovery on explicit opt-in

• Add lockfileRepairOptedIn() function to check if caller explicitly opted into lockfile repair
• Gate integrity mismatch recovery on explicit opt-in flags (fixLockfile, force) or `pnpm
 update`
• Update error handling in two catch blocks to throw on integrity mismatches unless opted in
• Preserve --frozen-lockfile behavior and auto-repair for missing dependencies

installing/deps-installer/src/install/index.ts


2. installing/deps-installer/test/brokenLockfileIntegrity.ts 🧪 Tests +15/-26

Test integrity mismatch failure and recovery paths

• Update test to verify default failure on integrity mismatch without opt-in flags
• Add test case for default failure behavior before --fix-lockfile recovery
• Replace preferFrozenLockfile: false with force: true to explicitly opt into repair
• Remove jest import and reporter-based warning assertions
• Simplify test expectations to focus on error throwing vs. recovery

installing/deps-installer/test/brokenLockfileIntegrity.ts


3. worker/src/index.ts Error handling +7/-4

Enhance error hint with security context

• Rewrite TarballIntegrityError hint to explain supply-chain security implications
• Point users to recovery options: pnpm store prune, --fix-lockfile, or manual verification
• Clarify that lockfile integrity is not advisory and should not be silently overwritten
• Add guidance to treat unexpected changes as potential supply-chain issues

worker/src/index.ts


View more (1)
4. .changeset/integrity-mismatch-fails-by-default.md 📝 Documentation +9/-0

Changelog entry for integrity mismatch hardening

• Document breaking change: tarball integrity mismatches now fail by default
• Explain previous behavior allowed silent registry substitution attacks
• List explicit opt-in flags for lockfile repair: --fix-lockfile, --force, pnpm update
• Note that --frozen-lockfile behavior remains unchanged

.changeset/integrity-mismatch-fails-by-default.md


Grey Divider

Qodo Logo

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
installing/deps-installer/test/brokenLockfileIntegrity.ts (1)

17-67: ⚡ Quick win

Add an explicit update: true recovery-path assertion.

These tests now cover default failure, fixLockfile, and force, but not the documented pnpm update opt-in path. Adding one assertion here would lock that contract.

✅ Minimal test addition
+  await mutateModulesInSingleProject({
+    manifest,
+    mutation: 'install',
+    rootDir: process.cwd() as ProjectRootDir,
+  }, testDefaults({ update: true }, { retry: { retries: 0 } }))

Also applies to: 69-110

🤖 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 `@installing/deps-installer/test/brokenLockfileIntegrity.ts` around lines 17 -
67, Add an explicit recovery-path assertion that exercising the "pnpm update"
opt-in (update: true) also repairs the corrupted lockfile: in the test function
test('installation fails by default if the lockfile contains a wrong checksum,
but --fix-lockfile recovers') after the existing force-path block (the
mutateModulesInSingleProject call using testDefaults({ force: true })), add
another mutateModulesInSingleProject invocation with testDefaults({ update: true
}, { retry: { retries: 0 } }) and then assert that project.readLockfile()
isStrictEqual to correctLockfile; repeat the same insertion for the sibling test
block referenced at the second location (lines 69-110) so both tests assert the
update: true recovery path.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@worker/src/index.ts`:
- Around line 142-149: The integrity hint string (the hint property in the code
block around where the tarball integrity message is defined) only mentions
"--fix-lockfile"; update that hint to list all supported explicit recovery
opt-ins by adding "--force" and "pnpm update" alongside the existing
"--fix-lockfile" and "pnpm store prune" suggestions so the CLI guidance is
complete and consistent.

---

Nitpick comments:
In `@installing/deps-installer/test/brokenLockfileIntegrity.ts`:
- Around line 17-67: Add an explicit recovery-path assertion that exercising the
"pnpm update" opt-in (update: true) also repairs the corrupted lockfile: in the
test function test('installation fails by default if the lockfile contains a
wrong checksum, but --fix-lockfile recovers') after the existing force-path
block (the mutateModulesInSingleProject call using testDefaults({ force: true
})), add another mutateModulesInSingleProject invocation with testDefaults({
update: true }, { retry: { retries: 0 } }) and then assert that
project.readLockfile() isStrictEqual to correctLockfile; repeat the same
insertion for the sibling test block referenced at the second location (lines
69-110) so both tests assert the update: true recovery path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e299ba00-0aee-4d5b-9a43-5039b3974168

📥 Commits

Reviewing files that changed from the base of the PR and between f107b1e and c782cf6.

📒 Files selected for processing (4)
  • .changeset/integrity-mismatch-fails-by-default.md
  • installing/deps-installer/src/install/index.ts
  • installing/deps-installer/test/brokenLockfileIntegrity.ts
  • worker/src/index.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). (1)
  • GitHub Check: Compile & Lint
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{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:

  • installing/deps-installer/test/brokenLockfileIntegrity.ts
  • worker/src/index.ts
  • installing/deps-installer/src/install/index.ts
🧠 Learnings (1)
📚 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:

  • installing/deps-installer/test/brokenLockfileIntegrity.ts
  • worker/src/index.ts
  • installing/deps-installer/src/install/index.ts
🔇 Additional comments (2)
installing/deps-installer/src/install/index.ts (1)

118-129: LGTM!

Also applies to: 1063-1071, 2006-2010

.changeset/integrity-mismatch-fails-by-default.md (1)

1-10: LGTM!

Comment thread worker/src/index.ts Outdated
zkochan and others added 2 commits May 26, 2026 20:59
Previously, plain `pnpm install` (non-frozen) reacted to
`ERR_PNPM_TARBALL_INTEGRITY` by silently re-resolving from the registry
and overwriting the locked integrity. A compromised registry, proxy, or
republished version could substitute attacker-controlled content on a
clean machine even though the project shipped a committed lockfile.

Treat integrity mismatches as a hard failure unless the caller opted in
via `--fix-lockfile`, `--force`, or `pnpm update`. `--frozen-lockfile`
behavior is unchanged.

Co-authored-by: Claude <noreply@anthropic.com>
Both flags also opt into lockfile repair. Listing them keeps the hint
consistent with the install-side logic that accepts them as recovery
opt-ins.
…e-checksums flag

--fix-lockfile is documented for filling in *missing* lockfile entries.
Reusing it to overwrite an existing-but-mismatched integrity conflates
two different operations: someone running --fix-lockfile to recover
from a half-merged lockfile shouldn't also be rubber-stamping registry-
tampered content.

Add --update-checksums (matching yarn's flag of the same name), scoped
narrowly to refreshing the locked integrity values from what the
registry currently serves. Make it the explicit opt-in for the
integrity-mismatch recovery path, drop --fix-lockfile from that path,
and update the TarballIntegrityError hint and tests accordingly.
--force and `pnpm update` remain implicit opt-ins.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
installing/deps-installer/test/brokenLockfileIntegrity.ts (1)

17-67: ⚡ Quick win

Add coverage for the remaining lockfile-repair branches.

These tests lock in default failure plus the updateChecksums and force opt-ins, but they still miss the other two load-bearing edges of this change: update: true should recover, and fixLockfile: true should still fail. One extra regression case for each would make this policy much harder to accidentally loosen later.

Possible test additions
+  await mutateModulesInSingleProject({
+    manifest,
+    mutation: 'install',
+    rootDir: process.cwd() as ProjectRootDir,
+    update: true,
+  }, testDefaults({}, { retry: { retries: 0 } }))
+
+  await expect(mutateModulesInSingleProject({
+    manifest,
+    mutation: 'install',
+    rootDir: process.cwd() as ProjectRootDir,
+  }, testDefaults({ fixLockfile: true }, { retry: { retries: 0 } }))).rejects.toThrow(/Got unexpected checksum/)

Also applies to: 69-110

🤖 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 `@installing/deps-installer/test/brokenLockfileIntegrity.ts` around lines 17 -
67, The test covers frozenLockfile, updateChecksums and force paths but misses
the update:true recovery and the fixLockfile:true failure branches; add two
assertions calling mutateModulesInSingleProject with testDefaults({ update: true
}, { retry: { retries: 0 } }) and expect it to succeed and update the lockfile
to correctLockfile, and another call with testDefaults({ fixLockfile: true }, {
retry: { retries: 0 } }) and expect it to reject with /Got unexpected checksum
for/; place these near the existing mutateModulesInSingleProject calls that
exercise updateChecksums and force so they reference the same manifest/manifest
variable and project.readLockfile() checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@installing/deps-installer/src/install/extendInstallOptions.ts`:
- Around line 54-62: The new required boolean option updateChecksums is not
initialized in defaults(), causing extendOptions()/ProcessedInstallOptions to
possibly return undefined; update defaults() in extendInstallOptions.ts to
include updateChecksums: false alongside the other boolean defaults so
ProcessedInstallOptions always has a boolean value, and ensure any places
merging or validating options (e.g., extendOptions or the defaults object) treat
updateChecksums the same as the existing boolean flags.

---

Nitpick comments:
In `@installing/deps-installer/test/brokenLockfileIntegrity.ts`:
- Around line 17-67: The test covers frozenLockfile, updateChecksums and force
paths but misses the update:true recovery and the fixLockfile:true failure
branches; add two assertions calling mutateModulesInSingleProject with
testDefaults({ update: true }, { retry: { retries: 0 } }) and expect it to
succeed and update the lockfile to correctLockfile, and another call with
testDefaults({ fixLockfile: true }, { retry: { retries: 0 } }) and expect it to
reject with /Got unexpected checksum for/; place these near the existing
mutateModulesInSingleProject calls that exercise updateChecksums and force so
they reference the same manifest/manifest variable and project.readLockfile()
checks.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 510041f6-89a4-42c7-8a15-4e81a71f3698

📥 Commits

Reviewing files that changed from the base of the PR and between fa3a733 and dbee46d.

📒 Files selected for processing (6)
  • .changeset/integrity-mismatch-fails-by-default.md
  • installing/commands/src/install.ts
  • installing/deps-installer/src/install/extendInstallOptions.ts
  • installing/deps-installer/src/install/index.ts
  • installing/deps-installer/test/brokenLockfileIntegrity.ts
  • worker/src/index.ts
✅ Files skipped from review due to trivial changes (2)
  • worker/src/index.ts
  • .changeset/integrity-mismatch-fails-by-default.md
📜 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). (2)
  • GitHub Check: Analyze (javascript)
  • GitHub Check: Compile & Lint
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{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:

  • installing/commands/src/install.ts
  • installing/deps-installer/src/install/extendInstallOptions.ts
  • installing/deps-installer/test/brokenLockfileIntegrity.ts
  • installing/deps-installer/src/install/index.ts
🧠 Learnings (1)
📚 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:

  • installing/commands/src/install.ts
  • installing/deps-installer/src/install/extendInstallOptions.ts
  • installing/deps-installer/test/brokenLockfileIntegrity.ts
  • installing/deps-installer/src/install/index.ts

Comment thread installing/deps-installer/src/install/extendInstallOptions.ts Outdated
…tch recovery

--force and `pnpm update` are routine refresh operations; silently
overwriting a locked integrity in those flows would erase the
protection a committed lockfile is supposed to provide. Reserve the
opt-in for --update-checksums, which is explicit about its intent.

When the bypass does kick in, surface a dedicated warning that names
--update-checksums so the lockfile rewrite stays auditable rather than
masquerading as a "broken lockfile" recovery.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
installing/deps-installer/src/install/index.ts (1)

1085-1090: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

--update-checksums currently falls into the normal update flow.

Setting project.update = true here makes the recovery path behave like a real update. _installInContext() later sees that flag at Lines 1387-1392 and drops preferredVersions, so a checksum repair can pull newer semver-matching versions instead of only re-fetching the existing locked package and refreshing its integrity. That makes this recovery path broader than the flag’s contract.

Also applies to: 2024-2028

🤖 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 `@installing/deps-installer/src/install/index.ts` around lines 1085 - 1090, The
recovery path for isIntegrityError is wrongly setting (project as
InstallMutationOptions).update = true which causes _installInContext to treat it
as a full update and drop preferredVersions; instead add a dedicated flag (e.g.,
refreshIntegrityOnly or forceIntegrityRefresh) to InstallMutationOptions and set
that for each project when isIntegrityError is true, then update
_installInContext to detect this new flag and perform only integrity
re-download/verification without clearing preferredVersions or engaging the
normal update flow; also add the new flag to the InstallMutationOptions type so
TypeScript recognizes it.
🤖 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.

Outside diff comments:
In `@installing/deps-installer/src/install/index.ts`:
- Around line 1085-1090: The recovery path for isIntegrityError is wrongly
setting (project as InstallMutationOptions).update = true which causes
_installInContext to treat it as a full update and drop preferredVersions;
instead add a dedicated flag (e.g., refreshIntegrityOnly or
forceIntegrityRefresh) to InstallMutationOptions and set that for each project
when isIntegrityError is true, then update _installInContext to detect this new
flag and perform only integrity re-download/verification without clearing
preferredVersions or engaging the normal update flow; also add the new flag to
the InstallMutationOptions type so TypeScript recognizes it.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 65a1bb75-8208-4f80-894c-f84c80f5ca63

📥 Commits

Reviewing files that changed from the base of the PR and between dbee46d and cfb23f8.

📒 Files selected for processing (4)
  • .changeset/integrity-mismatch-fails-by-default.md
  • installing/deps-installer/src/install/index.ts
  • installing/deps-installer/test/brokenLockfileIntegrity.ts
  • worker/src/index.ts
✅ Files skipped from review due to trivial changes (2)
  • worker/src/index.ts
  • .changeset/integrity-mismatch-fails-by-default.md
📜 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). (1)
  • GitHub Check: Compile & Lint
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{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:

  • installing/deps-installer/src/install/index.ts
  • installing/deps-installer/test/brokenLockfileIntegrity.ts
🧠 Learnings (1)
📚 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:

  • installing/deps-installer/src/install/index.ts
  • installing/deps-installer/test/brokenLockfileIntegrity.ts

Port the security-relevant half of #11968 to pacquet so the
two CLIs behave identically when refreshing the locked integrity from
the registry:

- New `--update-checksums` flag on `pacquet install`. Threaded through
  `Install` and forced onto the fresh-resolve dispatch path: the user
  has opted into rewriting locked integrity, so the frozen-lockfile
  fast path (which would refuse on mismatch) is skipped.
- `--update-checksums + --frozen-lockfile` is contradictory and now
  errors out with `pacquet_package_manager::frozen_lockfile_with_outdated_lockfile`,
  matching upstream's `ERR_PNPM_FROZEN_LOCKFILE_WITH_OUTDATED_LOCKFILE`.

Pacquet's existing frozen-path integrity check already fails-hard on
mismatch (no auto-repair to remove). The pacquet-side change here is
purely about CLI surface parity so `pnpm install --update-checksums`
keeps working when pacquet is the materialization target.
@github-actions

github-actions Bot commented May 26, 2026

Copy link
Copy Markdown
Contributor

Micro-Benchmark Results

Linux

group                          main                                   pr
-----                          ----                                   --
tarball/download_dependency    1.03      8.7±0.17ms   498.8 KB/sec    1.00      8.5±0.07ms   513.2 KB/sec

@codecov-commenter

codecov-commenter commented May 26, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.11765% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 88.04%. Comparing base (f578281) to head (60381e9).
⚠️ Report is 7 commits behind head on main.

Files with missing lines Patch % Lines
pacquet/crates/package-manager/src/install.rs 85.71% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #11968      +/-   ##
==========================================
+ Coverage   87.93%   88.04%   +0.10%     
==========================================
  Files         228      228              
  Lines       27810    28084     +274     
==========================================
+ Hits        24456    24727     +271     
- Misses       3354     3357       +3     

☔ 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 May 26, 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 1.954 ± 0.089 1.870 2.165 1.01 ± 0.05
pacquet@main 1.942 ± 0.055 1.899 2.069 1.00
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 1.9542787957,
      "stddev": 0.08916934332744293,
      "median": 1.9158370411,
      "user": 2.754301,
      "system": 3.2921192599999998,
      "min": 1.8699331731000002,
      "max": 2.1650845090999997,
      "times": [
        1.9997691861000002,
        1.9748819701000002,
        2.0150187611,
        1.9162068141000002,
        1.9154672681000002,
        1.8916359821000002,
        2.1650845090999997,
        1.8699331731000002,
        1.8820219261000002,
        1.9127683671000002
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 1.9417368341000003,
      "stddev": 0.05454299327247572,
      "median": 1.9221472441,
      "user": 2.7964125,
      "system": 3.2418863599999996,
      "min": 1.8994226471000002,
      "max": 2.0688454381,
      "times": [
        1.9110080611000002,
        1.9310053671000003,
        1.9452644471000002,
        2.0688454381,
        1.9226616071,
        1.9034175911000002,
        1.8994226471000002,
        1.9216328811,
        1.9068068161000002,
        2.0073034851
      ]
    }
  ]
}

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

Command Mean [ms] Min [ms] Max [ms] Relative
pacquet@HEAD 645.8 ± 44.8 623.7 772.3 1.00
pacquet@main 648.7 ± 15.5 630.2 686.5 1.00 ± 0.07
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 0.6458038076800001,
      "stddev": 0.04479281631279863,
      "median": 0.63366634598,
      "user": 0.35731345999999997,
      "system": 1.34766282,
      "min": 0.62370076298,
      "max": 0.77232434298,
      "times": [
        0.77232434298,
        0.6409137499800001,
        0.63604480898,
        0.6345648659800001,
        0.63276782598,
        0.63556860298,
        0.62758949698,
        0.6307115639800001,
        0.62370076298,
        0.62385205598
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 0.6486854950800001,
      "stddev": 0.01545684674654336,
      "median": 0.6452477114799999,
      "user": 0.37025425999999995,
      "system": 1.3504832199999997,
      "min": 0.63017197198,
      "max": 0.68652222698,
      "times": [
        0.68652222698,
        0.65824169798,
        0.63017197198,
        0.64302314798,
        0.64347263498,
        0.64059175298,
        0.64702278798,
        0.65026475198,
        0.6364833439800001,
        0.65106063398
      ]
    }
  ]
}

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

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 2.276 ± 0.028 2.242 2.327 1.00
pacquet@main 2.344 ± 0.039 2.281 2.393 1.03 ± 0.02
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 2.276156374,
      "stddev": 0.027812131754936388,
      "median": 2.2793784706,
      "user": 3.86260658,
      "system": 3.0377359000000004,
      "min": 2.2418023756000003,
      "max": 2.3272427986000004,
      "times": [
        2.3272427986000004,
        2.2824549306,
        2.2419147736,
        2.2418023756000003,
        2.2898825216,
        2.2955959986,
        2.2662141336,
        2.2453752526,
        2.2763020106000003,
        2.2947789446
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 2.3439786531,
      "stddev": 0.03905237145779966,
      "median": 2.3378419116,
      "user": 3.976436079999999,
      "system": 3.0657900000000007,
      "min": 2.2811500836,
      "max": 2.3929052306000003,
      "times": [
        2.2811500836,
        2.3313201236000003,
        2.3112311346000003,
        2.3911554556000003,
        2.3108134196,
        2.3929052306000003,
        2.3839498446,
        2.3443636996,
        2.3217882626,
        2.3711092766000004
      ]
    }
  ]
}

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

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 1.516 ± 0.063 1.463 1.686 1.03 ± 0.05
pacquet@main 1.478 ± 0.032 1.446 1.540 1.00
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 1.51558008742,
      "stddev": 0.06299410079990962,
      "median": 1.50515108542,
      "user": 1.78644238,
      "system": 1.8510429,
      "min": 1.4634676179200001,
      "max": 1.68597797692,
      "times": [
        1.5124577379200002,
        1.68597797692,
        1.49130711992,
        1.5178775799200002,
        1.4927408179200001,
        1.50476787592,
        1.5055342949200001,
        1.4644260659200001,
        1.5172437869200002,
        1.4634676179200001
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 1.4778804275200002,
      "stddev": 0.03185364323022722,
      "median": 1.4720621454200002,
      "user": 1.7694980799999995,
      "system": 1.8342413,
      "min": 1.4462332659200001,
      "max": 1.5397847609200002,
      "times": [
        1.5397847609200002,
        1.4742204309200002,
        1.5246944539200002,
        1.4491188489200002,
        1.4895284999200002,
        1.4462332659200001,
        1.46990385992,
        1.45985155592,
        1.47462696692,
        1.4508416319200002
      ]
    }
  ]
}

@github-actions

github-actions Bot commented May 26, 2026

Copy link
Copy Markdown
Contributor

🐰 Bencher Report

Branchpr/11968
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,276.16 ms
(-9.19%)Baseline: 2,506.59 ms
3,007.91 ms
(75.67%)
isolated-linker.fresh-install.hot-cache.hot-store📈 view plot
🚷 view threshold
1,515.58 ms
(-8.13%)Baseline: 1,649.72 ms
1,979.66 ms
(76.56%)
isolated-linker.fresh-restore.cold-cache.cold-store📈 view plot
🚷 view threshold
1,954.28 ms
(-3.92%)Baseline: 2,033.93 ms
2,440.72 ms
(80.07%)
isolated-linker.fresh-restore.hot-cache.hot-store📈 view plot
🚷 view threshold
645.80 ms
(-2.09%)Baseline: 659.62 ms
791.54 ms
(81.59%)
🐰 View full continuous benchmarking report in Bencher

zkochan added 5 commits May 27, 2026 00:00
The recovery-path setting of project.update = true (inherited from
pre-existing code) is dead under the new --update-checksums gating:

- Inner catch is unreachable for integrity errors because
  needsFullResolution = true (set by --update-checksums) makes
  isFrozenInstallPossible false, so tryFrozenInstall returns null
  without entering its try block.
- Outer catch's integrity-recovery branch is unreachable because the
  preceding _installInContext call (with needsFullResolution = true)
  re-queries metadata via forceFullResolution → proceed and refreshes
  the integrity in the wanted lockfile before fetch.

Setting project.update would also drop preferredVersions and let the
resolver bump locked versions within range — broader than
--update-checksums asks for. Remove the lines.

Rename lockfileRepairOptedIn → integrityRefreshOptedIn so the call
site reads on its own, and trim the surrounding comments and help text
that were doing the job a good name and types already do.
The StrictInstallOptions field is non-optional, but extendOptions() was
leaving it undefined on the cast — making the runtime value diverge
from the type contract. Add an explicit default alongside the other
booleans.
Under --update-checksums gating, the inner catch's integrity-error
branch is effectively unreachable: opts.updateChecksums sets
needsFullResolution=true at line 600, which makes tryFrozenInstall
return null without entering its try block. The only path that
reliably reaches this catch is LOCKFILE_MISSING_DEPENDENCY (bad-Git-
merge recovery), where the original 'lockfile is broken' message is
the right one.

Also drop the now-dead `if (isIntegrityError) { needsFullResolution =
true }` block — under the only reachable opt-in path that *would* hit
this catch (ignorePackageManifest combined with --update-checksums),
needsFullResolution is already true from the line-600 assignment.

The outer catch keeps its dedicated 'Refreshing the locked integrity
...' message, since that path is the one that actually exists for
the user-opted-in recovery (TOCTOU race between resolve and fetch).
Mirrors the hint pnpm's TarballIntegrityError now emits when an
integrity mismatch is surfaced. Same recovery flag, same supply-chain
caveat, so a user hitting the failure from either CLI gets the same
guidance.
@zkochan

zkochan commented May 26, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 26, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pacquet/crates/package-manager/src/install.rs (1)

402-410: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

--update-checksums is bypassed by the optimistic up-to-date short-circuit.

When update_checksums is true, this early return can still emit “Already up to date” and skip the fresh-resolve path entirely, so checksum refresh is not guaranteed.

Suggested fix
-        if !frozen_lockfile
+        if !frozen_lockfile
+            && !update_checksums
             && let OptimisticRepeatInstallDecision::UpToDate = check_optimistic_repeat_install(
                 &workspace_root,
                 config,
                 node_linker,
                 included,
                 &project_manifests,
                 workspace_manifest.is_some(),
             )
🤖 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/package-manager/src/install.rs` around lines 402 - 410, The
optimistic up-to-date short-circuit currently returns when
check_optimistic_repeat_install(...) yields
OptimisticRepeatInstallDecision::UpToDate even if update_checksums is true,
which lets "--update-checksums" be bypassed; update the early-return conditional
around frozen_lockfile and OptimisticRepeatInstallDecision::UpToDate (the if
that calls check_optimistic_repeat_install) to also require that
update_checksums is false (e.g., consult the config.update_checksums flag) so
the short-circuit is skipped when update_checksums is set, ensuring the
fresh-resolve/checksum-refresh path always runs when update_checksums is true.
🤖 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.

Outside diff comments:
In `@pacquet/crates/package-manager/src/install.rs`:
- Around line 402-410: The optimistic up-to-date short-circuit currently returns
when check_optimistic_repeat_install(...) yields
OptimisticRepeatInstallDecision::UpToDate even if update_checksums is true,
which lets "--update-checksums" be bypassed; update the early-return conditional
around frozen_lockfile and OptimisticRepeatInstallDecision::UpToDate (the if
that calls check_optimistic_repeat_install) to also require that
update_checksums is false (e.g., consult the config.update_checksums flag) so
the short-circuit is skipped when update_checksums is set, ensuring the
fresh-resolve/checksum-refresh path always runs when update_checksums is true.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e99d6330-ed66-42b3-bb54-d89e62789eb8

📥 Commits

Reviewing files that changed from the base of the PR and between dbee46d and 25117c7.

📒 Files selected for processing (11)
  • .changeset/integrity-mismatch-fails-by-default.md
  • installing/commands/src/install.ts
  • installing/deps-installer/src/install/extendInstallOptions.ts
  • installing/deps-installer/src/install/index.ts
  • installing/deps-installer/test/brokenLockfileIntegrity.ts
  • pacquet/crates/cli/src/cli_args/install.rs
  • pacquet/crates/package-manager/src/add.rs
  • pacquet/crates/package-manager/src/install.rs
  • pacquet/crates/package-manager/src/install/tests.rs
  • pacquet/crates/tarball/src/lib.rs
  • worker/src/index.ts
✅ Files skipped from review due to trivial changes (3)
  • pacquet/crates/tarball/src/lib.rs
  • worker/src/index.ts
  • .changeset/integrity-mismatch-fails-by-default.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • installing/deps-installer/src/install/extendInstallOptions.ts
  • installing/deps-installer/test/brokenLockfileIntegrity.ts
  • installing/commands/src/install.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). (1)
  • GitHub Check: ubuntu-latest / Node.js 24 / Test
🧰 Additional context used
📓 Path-based instructions (2)
pacquet/**/*.rs

📄 CodeRabbit inference engine (pacquet/AGENTS.md)

pacquet/**/*.rs: When porting a function that fires pnpm:<channel> events through globalLogger, logger.debug(), or streamParser.write(), mirror the call site, payload, and ordering so the reporter parses pacquet's NDJSON the same way it parses pnpm's.
Declare a newtype wrapper for branded string types. Do not collapse the brand into a plain String or &str.
If upstream always validates before construction, validate in pacquet's wrapper too. The wrapper must construct only via TryFrom<String> and/or FromStr. Do not provide an infallible public constructor.
If upstream never validates, just brand for type-safety. Expose an infallible From<String> (and From<&str> when convenient).
If upstream occasionally constructs without validation, expose from_str_unchecked as an escape hatch alongside the validating constructor.
Match upstream serde behavior for branded types that cross JSON, YAML, or INI boundaries. Use #[serde(try_from = "String")] for deserialization and #[serde(into = "String")] for serialization.
Use #[derive(derive_more::From)] and #[derive(derive_more::Into)] for mechanical conversion impls. Fall back to manual impl only when conversion needs custom logic.
String-literal unions should become enums, not newtype wrappers. Model closed sets of valid string values as enums.
Template literal types should be treated as branded strings with validation discipline from rules 2-5.
Choose owned vs. borrowed parameters to minimize copies. Widen to the most encompassing type (&Path over &PathBuf, &str over &String) when it doesn't force extra copies.
Prefer Arc::clone(&x) / Rc::clone(&x) over x.clone() for reference-counted types, so the cost is visible at the call site.
Follow Rust API Guidelines for naming conventions.
Do not use star imports inside module bodies. Write use super::{Foo, bar} instead of use super::*;. Two forms stay allowed: external-crate preludes like use rayon::prelude::*; and root-of-module re-...

Files:

  • pacquet/crates/package-manager/src/add.rs
  • pacquet/crates/cli/src/cli_args/install.rs
  • pacquet/crates/package-manager/src/install.rs
  • pacquet/crates/package-manager/src/install/tests.rs
**/*.{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:

  • installing/deps-installer/src/install/index.ts
🧠 Learnings (4)
📚 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/package-manager/src/add.rs
  • pacquet/crates/cli/src/cli_args/install.rs
  • pacquet/crates/package-manager/src/install.rs
  • pacquet/crates/package-manager/src/install/tests.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/package-manager/src/add.rs
  • pacquet/crates/cli/src/cli_args/install.rs
  • pacquet/crates/package-manager/src/install.rs
  • pacquet/crates/package-manager/src/install/tests.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/package-manager/src/add.rs
  • pacquet/crates/cli/src/cli_args/install.rs
  • pacquet/crates/package-manager/src/install.rs
  • pacquet/crates/package-manager/src/install/tests.rs
📚 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:

  • installing/deps-installer/src/install/index.ts
🔇 Additional comments (8)
installing/deps-installer/src/install/index.ts (3)

592-597: LGTM!


1051-1060: LGTM!


1987-2000: LGTM!

pacquet/crates/cli/src/cli_args/install.rs (1)

177-182: LGTM!

Also applies to: 215-216, 279-280

pacquet/crates/package-manager/src/add.rs (1)

109-109: LGTM!

pacquet/crates/package-manager/src/install.rs (1)

110-113: LGTM!

Also applies to: 230-235, 312-313, 608-610, 628-629

pacquet/crates/package-manager/src/install/tests.rs (2)

147-189: LGTM!


66-66: LGTM!

Also applies to: 135-135, 252-252, 318-318, 401-401, 469-469, 557-557, 702-702, 803-803, 1002-1002, 1075-1075, 1171-1171, 1301-1301, 1399-1399, 1505-1505, 1555-1555, 1647-1647, 1741-1741, 1805-1805, 1870-1870, 1961-1961, 2068-2068, 2129-2129, 2217-2217, 2324-2324, 2438-2438, 2518-2518, 2718-2718, 2842-2842, 2942-2942, 3048-3048, 3139-3139, 3233-3233, 3324-3324, 3412-3412, 3506-3506, 3598-3598, 3696-3696, 3796-3796, 3882-3882, 3966-3966, 4036-4036, 4102-4102, 4167-4167, 4235-4235, 4319-4319, 4381-4381, 4466-4466, 4517-4517, 4588-4588, 4660-4660, 4726-4726, 4978-4978, 5159-5159, 5307-5307, 5456-5456, 5534-5534, 5585-5585

zkochan added 2 commits May 27, 2026 00:47
Without this, the resolver short-circuits the registry when its
in-memory or on-disk metadata cache already has the wanted version
pinned, handing back whatever integrity was cached. That defeats
--update-checksums on dev machines where the cache is the very thing
that's potentially stale.

Plumb updateChecksums from the install options through resolver-base,
deps-resolver, store-controller-types, and npm-resolver into
pickPackage, then gate the two fast paths (in-memory metaCache hit at
line 209 and exact-version on-disk hit at line 267) on
!opts.updateChecksums. With the flag set, every resolution goes
through the network fetch with conditional headers, so the registry
is the authority on what integrity to record.

The conditional-request flow stays intact: if registry content hasn't
changed it returns 304 and pacquet reuses cache; if it has, 200 with
fresh metadata wins.
…s cache bypass

Pacquet's pick_package mirrors upstream pnpm's same four-layer cache
walk before any network fetch. Without gating, --update-checksums on
pacquet has the same blind spot as pnpm had before the previous
commit: a stale in-memory or on-disk cached packument with the wanted
version pinned would short-circuit the registry entirely, handing
back a possibly-stale integrity.

Thread update_checksums from ResolveOptions into PickPackageOptions
and gate steps 1 (in-memory cache) and 3 (version-spec on-disk fast
path) on !opts.update_checksums. Plumb through Install →
InstallWithFreshLockfile → ResolveOptions so the existing
pacquet/cli/install --update-checksums flag now also bypasses the
metadata cache, matching the pnpm-side behavior.

Step 4 (publishedBy mtime shortcut) is intentionally left untouched
on both sides — it's the maturity-filter cache, not the
integrity-source cache, and the upstream pnpm fix doesn't gate it
either.
@github-actions

Copy link
Copy Markdown
Contributor

Benchmark Results

# Scenario main HEAD
1 Isolated linker: fresh restore, hot cache + hot store 2.347s ± 0.144s 2.299s ± 0.066s
2 Isolated linker: fresh add new dep, hot cache + hot store 5.375s ± 0.058s 5.41s ± 0.046s
3 Isolated linker: fresh install, hot cache + hot store 5.567s ± 0.05s 5.583s ± 0.043s
4 Isolated linker: fresh restore, cold cache + cold store 4.916s ± 0.059s 4.905s ± 0.133s
5 Isolated linker: fresh install, cold cache + cold store 7.883s ± 0.313s 7.735s ± 0.066s
6 GVS linker: fresh restore, hot cache + hot store 1.1s ± 0.058s 1.083s ± 0.011s

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

The in-memory metaCache is constructed per-install (empty at start)
and only the resolver's own pickPackage writes to it — populating it
from network fetches that already respect the disk fast-path gate.
Entries are therefore fresh by construction; gating reads from the
cache just forces redundant conditional requests for second-and-onward
references to the same package.

Drop the gate on both pnpm (pickPackage.ts:215) and pacquet
(pick_package.rs:419, 461). The disk fast-path gates remain — those
are the ones that actually force a registry round-trip on first
encounter.
@zkochan zkochan merged commit aa6149d into main May 27, 2026
23 of 25 checks passed
@zkochan zkochan deleted the vuln7 branch May 27, 2026 10:46
zkochan added a commit that referenced this pull request May 27, 2026
…ty (#11985)

Backport of #11968 to release/10. Treats tarball-integrity mismatches
against the lockfile as a hard failure by default; `--update-checksums`
is the only opt-in. `--force` and `pnpm update` deliberately do not
bypass the integrity check.

🤖 Written by an agent (Claude Code, claude-opus-4-7).
@zkochan zkochan mentioned this pull request May 27, 2026
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.

2 participants