Skip to content

fix(manifest): reject invalid dependency specifiers during install - #16416

Merged
zkochan merged 3 commits into
pnpm:mainfrom
tunglambk:fix/reject-invalid-dependency-specifiers
Oct 5, 2026
Merged

zkochan merged 3 commits into
pnpm:mainfrom
tunglambk:fix/reject-invalid-dependency-specifiers

Conversation

@tunglambk

@tunglambk tunglambk commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Summary

pnpm install --lockfile-only on a project with "dependencies": {"is-positive": 42} currently exits successfully and writes a lockfile without is-positive. A later frozen install also succeeds, so the malformed declaration can go unnoticed in CI.

Validate the specifiers in the four dependency groups after readPackage hooks run and before settling the lockfile. A group that is not an object (for example "devDependencies": null) is still accepted as declaring no dependencies, matching pnpm v11. A malformed declaration bypasses the repeat-install shortcut so a hook can repair it; otherwise installation reports ERR_PNPM_PACKAGE_MANIFEST_INVALID_ATTRIBUTE. pnpm v11 already fails on a non-string specifier, so the change applies to the Rust v12 CLI.

The regression test failed on the target branch and passes with the fix. Added coverage for frozen installs, hook repair, and all four dependency groups. Local checks passed: 142 package-manifest tests, 884 package-manager tests (one ignored), 18 install-configuration integration tests, workspace cargo check, workspace cargo clippy --deny warnings, and pinned rustfmt.

Squash Commit Body

The Rust install path read dependency specifiers through a filter that skipped non-string values. That made a malformed project manifest appear dependency-free and allowed both lockfile-only and frozen installs to succeed.

Check dependency specifier types after readPackage hooks have had a chance to correct the manifest. Disable the optimistic repeat-install shortcut for malformed declarations so it cannot bypass validation. Keep manifest reading permissive for commands that repair package.json, and keep accepting non-object dependency groups as pnpm v11 does.

Checklist

  • New features are implemented only in the Rust pnpm v12 CLI. Bug fixes are implemented in every affected version.
  • Added a changeset (pnpm changeset) if this PR changes any published package. Keep it short and written for pnpm users — it becomes a release note.
  • Added or updated tests.

Written by an agent (Codex, GPT-6).

A formatting fix was pushed by a different agent (DeepSeek Harness, deepseek-v4.1-flash). The non-object group fix was pushed by Claude Code (claude-opus-5-5).


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Summary by CodeRabbit

  • Bug Fixes
    • Installs now report an error when a dependency specifier is not a string, identifying the affected dependency.
    • Regular and frozen installs validate dependency specifiers; a failed regular install does not create a lockfile.
    • A readPackage hook can correct an invalid dependency specifier before validation, allowing installation to continue with the corrected dependency.
    • Dependency groups that are not objects remain skipped during dependency processing.

@coderabbitai

coderabbitai Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

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
🧰 Additional context used
📚 Code guidelines (3)
AGENTS.md — auto-discovered
pnpm/AGENTS.md — auto-discovered
pnpm/CODE_STYLE_GUIDE.md — auto-discovered

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: pnpm/pnpm/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 1061f251-97fb-4602-bbc6-5e06d01c6920
📥 Commits

Reviewing files that changed from the base of the PR and between fd788c1 and 1da1604.

📒 Files selected for processing (4)
  • .changeset/reject-invalid-dependency-specifiers.md
  • pnpm/crates/package-manifest/src/lib.rs
  • pnpm/crates/package-manifest/src/tests/dependencies.rs
  • pnpm/crates/package-manifest/src/validation.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • .changeset/reject-invalid-dependency-specifiers.md
  • pnpm/crates/package-manifest/src/lib.rs

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 3 remain after this review.


📝 Walkthrough

Walkthrough

Package manifests now validate dependency specifier types. Install execution applies this validation before lockfile settlement and propagates validation errors. Tests cover invalid specifiers and a readPackage hook that repairs one.

Changes

Dependency manifest validation

Layer / File(s) Summary
Dependency type validation
pnpm/crates/package-manifest/src/validation.rs, pnpm/crates/package-manifest/src/lib.rs, pnpm/crates/package-manifest/src/tests/dependencies.rs
Adds validation for specifiers in dependencies, devDependencies, optionalDependencies, and peerDependencies. Non-object groups are skipped. Tests cover accepted values and invalid non-string specifiers.
Install validation and coverage
pnpm/crates/package-manager/src/install/errors.rs, pnpm/crates/package-manager/src/install/run.rs, pnpm/crates/package-manager/src/install/run/manifest_validation.rs, pnpm/crates/package-manager/src/install/run/execution.rs, pnpm/crates/package-manager/src/install/run/fast_path.rs, pnpm/crates/cli/tests/suite/install/configuration.rs, .changeset/reject-invalid-dependency-specifiers.md
Install execution validates project manifests before lockfile settlement and propagates validation errors. Repeat-install checks account for malformed declarations. Tests cover lockfile-only and frozen installs, plus a readPackage hook that repairs a dependency to a local link.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Suggested labels: product: pacquet

Merge Risk: ⚪ Minimal · up to 1da16

The change makes pnpm install fail on non-string dependency specifiers instead of silently omitting them from the lockfile. A readPackage hook can still repair such a value. No merge-blocking risk was identified.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 1da16

The change strengthens validation before installation records dependency state, while preserving hook-based repair and lockfile-driven commands. No introduced security issue was substantiated. Concurrent-install and abrupt-interruption behavior remain only partially established.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The changed control applies to project manifests selected for an installing run and their downstream dependency state. Manifest-controlled values reach a read-only type check and diagnostic, not a newly privileged operation. The inspected implementation adds no credential, network, or infrastructure authority.

Trust Boundaries and Controls

  • observed — The new check enforces dependency value types after hook transformations; it does not authorize hooks or validate string-specifier destinations. Malformed declarations now force the existing hook-aware path instead of permitting an optimistic success based on filtered dependency inputs.

Resilience and Maintainability Implications

  • inferred — The validator introduces no mutable state or asynchronous interruption point, and repetition rechecks the current manifest view. Its failure containment is narrower than whole-install atomicity: existing hooks and workspace registration can occur before validation. Their side effects and cross-process crash recovery are not established as transactional by this review.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR

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.

@codecov-commenter

codecov-commenter commented Sep 30, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.32%. Comparing base (afe5cef) to head (1da1604).
⚠️ Report is 4 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main   #16416   +/-   ##
=======================================
  Coverage   92.32%   92.32%           
=======================================
  Files        1577     1579    +2     
  Lines      208925   208973   +48     
=======================================
+ Hits       192882   192932   +50     
+ Misses      16043    16041    -2     

☔ View full report in Codecov by Harness.
📢 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.

@tunglambk
tunglambk force-pushed the fix/reject-invalid-dependency-specifiers branch from 1934737 to 1009af9 Compare September 30, 2026 03:03
@github-actions

github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Same-run regression check

HEAD and main are measured on the same runner. The tolerance is 5% of the main median or 2 ms, whichever is larger. A regression requires both the median slowdown and the gap between the fastest retained HEAD sample and slowest retained main sample to exceed this tolerance, after trimming up to 10% from each tail. Suspected regressions are rerun once with target order reversed and only fail if reproduced. This conservative noise guard is not a statistical confidence interval.

Inconclusive rows do not fail the check. They remain visible for performance review. Bencher retains absolute minimum timings for historical tracking; historical alerts do not gate this PR. Sequential measurements can still be affected by runner drift.

Scenario Engine main median HEAD median Change Tolerance Result
isolated-fresh-restore-cold-cache-cold-store pacquet 2598.33 ms 2551.74 ms -1.8% 129.92 ms Within tolerance
isolated-fresh-restore-cold-cache-cold-store pnpr 1533.43 ms 1449.56 ms -5.5% 76.67 ms Within tolerance
isolated-fresh-restore-hot-cache-hot-store pacquet 417.53 ms 471.27 ms 12.9% 20.88 ms Inconclusive (overlapping samples)
isolated-fresh-restore-hot-cache-hot-store pnpr 344.62 ms 330.24 ms -4.2% 17.23 ms Within tolerance
isolated-repeat-install-hot-cache-hot-store pacquet 12.52 ms 13.11 ms 4.8% 2.00 ms Within tolerance
isolated-repeat-install-hot-cache-hot-store pnpr 11.34 ms 12.74 ms 12.4% 2.00 ms Within tolerance
isolated-repeat-install-cold-cache-hot-store pacquet 15.25 ms 14.76 ms -3.2% 2.00 ms Within tolerance
isolated-repeat-install-cold-cache-hot-store pnpr 16.09 ms 16.24 ms 0.9% 2.00 ms Within tolerance
isolated-fresh-install-cold-cache-cold-store pacquet 2757.81 ms 2690.21 ms -2.5% 137.89 ms Within tolerance
isolated-fresh-install-cold-cache-cold-store pnpr 1793.41 ms 1557.57 ms -13.2% 89.67 ms Within tolerance
isolated-fresh-install-hot-cache-hot-store pacquet 927.06 ms 811.61 ms -12.5% 46.35 ms Within tolerance
isolated-fresh-install-hot-cache-hot-store pnpr 435.44 ms 497.94 ms 14.4% 21.77 ms Inconclusive (not reproduced)
isolated-fresh-install-cold-cache-hot-store pacquet 2068.84 ms 2001.62 ms -3.2% 103.44 ms Within tolerance
isolated-fresh-install-cold-cache-hot-store pnpr 481.67 ms 427.73 ms -11.2% 24.08 ms Within tolerance
isolated-fresh-resolve-hot-cache-offline pacquet 330.08 ms 302.30 ms -8.4% 16.50 ms Within tolerance
isolated-fresh-resolve-hot-cache-offline pnpr 122.54 ms 114.03 ms -6.9% 6.13 ms Within tolerance
isolated-peer-heavy-resolve-hot-cache-offline pacquet 276.84 ms 310.12 ms 12.0% 13.84 ms Inconclusive (overlapping samples)
isolated-linked-workspace-resolve-hot-cache-offline pacquet 242.66 ms 260.21 ms 7.2% 12.13 ms Inconclusive (overlapping samples)
isolated-fresh-restore-cold-cache-cold-store-cold-pnpr pacquet 4694.84 ms 4526.95 ms -3.6% 234.74 ms Within tolerance
isolated-fresh-restore-cold-cache-cold-store-cold-pnpr pnpr 3571.95 ms 3646.97 ms 2.1% 178.60 ms Within tolerance
gvs-fresh-restore-hot-cache-hot-store pacquet 422.04 ms 483.91 ms 14.7% 21.10 ms Inconclusive (overlapping samples)
gvs-fresh-restore-hot-cache-hot-store pnpr 221.77 ms 256.52 ms 15.7% 11.09 ms Inconclusive (overlapping samples)

Integrated-Benchmark Report (Linux)

Commit: 1da16044611f

Regular scenarios report direct and pnpr installs. The peer-heavy resolver scenario compares current Rust, main Rust, and TypeScript pnpm; the linked-workspace scenario compares current Rust against main Rust. Bencher consumes pacquet@HEAD and pnpr@HEAD.

The tables below show mean ± σ. Bencher tracks absolute minimum latency; PR regression checks compare HEAD and main samples from the same runner. Historical timing alerts do not gate PRs.

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

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 2.585 ± 0.119 2.425 2.782 1.74 ± 0.18
pacquet@main 2.630 ± 0.119 2.437 2.833 1.77 ± 0.18
pnpr@HEAD 1.489 ± 0.137 1.362 1.818 1.00
pnpr@main 1.555 ± 0.141 1.369 1.742 1.04 ± 0.13
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 2.58496891334,
      "stddev": 0.11938740894484227,
      "median": 2.5517389327399997,
      "user": 2.23592878,
      "system": 2.13610028,
      "min": 2.4251325917399997,
      "max": 2.78242060274
    },
    {
      "command": "pacquet@main",
      "mean": 2.63018490564,
      "stddev": 0.11923383223053997,
      "median": 2.5983330907399997,
      "user": 2.2911168799999997,
      "system": 2.00406878,
      "min": 2.4374393197399997,
      "max": 2.83259059074
    },
    {
      "command": "pnpr@HEAD",
      "mean": 1.48918022714,
      "stddev": 0.13700813098271897,
      "median": 1.44955985474,
      "user": 1.9041612799999998,
      "system": 1.8287870800000001,
      "min": 1.3618438417399998,
      "max": 1.81823962574
    },
    {
      "command": "pnpr@main",
      "mean": 1.55450342594,
      "stddev": 0.14116013161545676,
      "median": 1.53342835774,
      "user": 1.7732795799999999,
      "system": 1.56826468,
      "min": 1.3693096107399998,
      "max": 1.74231250374
    }
  ]
}

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

Command Mean [ms] Min [ms] Max [ms] Relative
pacquet@HEAD 478.3 ± 55.1 397.5 555.0 1.42 ± 0.23
pacquet@main 431.9 ± 68.8 350.4 540.7 1.28 ± 0.25
pnpr@HEAD 337.6 ± 39.7 286.6 403.9 1.00
pnpr@main 340.2 ± 18.6 317.9 377.4 1.01 ± 0.13
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 0.4782792625400001,
      "stddev": 0.055076706159470114,
      "median": 0.47127401324000007,
      "user": 0.5541088000000001,
      "system": 0.9618801400000001,
      "min": 0.39750233674,
      "max": 0.55500731374
    },
    {
      "command": "pacquet@main",
      "mean": 0.4319133655400001,
      "stddev": 0.06876966620801953,
      "median": 0.41752734624000004,
      "user": 0.49246239999999997,
      "system": 0.94969214,
      "min": 0.35041284974000003,
      "max": 0.54065598674
    },
    {
      "command": "pnpr@HEAD",
      "mean": 0.33762437364000003,
      "stddev": 0.039707985975331726,
      "median": 0.33024181874,
      "user": 0.3953228,
      "system": 0.72276824,
      "min": 0.28662089674,
      "max": 0.40393037274000004
    },
    {
      "command": "pnpr@main",
      "mean": 0.34022724064000004,
      "stddev": 0.018622308458317274,
      "median": 0.34461614924000006,
      "user": 0.39552319999999996,
      "system": 0.71205054,
      "min": 0.31788843274000006,
      "max": 0.37738038574000005
    }
  ]
}

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

Command Mean [ms] Min [ms] Max [ms] Relative
pacquet@HEAD 13.1 ± 1.3 10.6 16.6 1.14 ± 0.15
pacquet@main 12.7 ± 1.6 9.4 17.5 1.10 ± 0.17
pnpr@HEAD 12.9 ± 1.6 9.4 17.2 1.12 ± 0.18
pnpr@main 11.6 ± 1.1 9.5 15.4 1.00
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 0.01314975606895833,
      "stddev": 0.0012603480509404874,
      "median": 0.01311323122,
      "user": 0.007307915833333332,
      "system": 0.007353552083333335,
      "min": 0.01055378172,
      "max": 0.016618775720000003
    },
    {
      "command": "pacquet@main",
      "mean": 0.012652790435686278,
      "stddev": 0.0015786272272089968,
      "median": 0.012518456720000001,
      "user": 0.007265436666666669,
      "system": 0.006805005686274512,
      "min": 0.00939323672,
      "max": 0.01750969872
    },
    {
      "command": "pnpr@HEAD",
      "mean": 0.01289791511473684,
      "stddev": 0.0016291464904458729,
      "median": 0.012742630720000002,
      "user": 0.006961635789473683,
      "system": 0.007384250947368421,
      "min": 0.009364333720000001,
      "max": 0.017152228720000003
    },
    {
      "command": "pnpr@main",
      "mean": 0.011552512952876714,
      "stddev": 0.0011118472122350566,
      "median": 0.01134109672,
      "user": 0.006653204931506848,
      "system": 0.006177777534246575,
      "min": 0.009461653720000001,
      "max": 0.015419394720000002
    }
  ]
}

Scenario: Isolated linker: repeat install, cold cache + hot store

Command Mean [ms] Min [ms] Max [ms] Relative
pacquet@HEAD 14.7 ± 1.9 11.1 19.5 1.00
pacquet@main 15.3 ± 1.9 10.8 21.1 1.04 ± 0.19
pnpr@HEAD 16.2 ± 1.5 12.6 19.6 1.10 ± 0.18
pnpr@main 16.0 ± 1.7 12.5 21.3 1.09 ± 0.18
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 0.014716992162068966,
      "stddev": 0.0019181080368622899,
      "median": 0.014758927900000001,
      "user": 0.008113046206896549,
      "system": 0.00821852206896552,
      "min": 0.011145192900000002,
      "max": 0.019544617900000002
    },
    {
      "command": "pacquet@main",
      "mean": 0.015346116249999995,
      "stddev": 0.001865532670470421,
      "median": 0.015246872400000002,
      "user": 0.008702174285714282,
      "system": 0.008272245714285717,
      "min": 0.0107779189,
      "max": 0.0210608429
    },
    {
      "command": "pnpr@HEAD",
      "mean": 0.016175009656249992,
      "stddev": 0.001481028477748176,
      "median": 0.0162434714,
      "user": 0.009105847499999998,
      "system": 0.00878352625,
      "min": 0.012608423900000002,
      "max": 0.0196280409
    },
    {
      "command": "pnpr@main",
      "mean": 0.01598363341492537,
      "stddev": 0.0016745127119314291,
      "median": 0.0160926624,
      "user": 0.009334870447761196,
      "system": 0.008418589552238809,
      "min": 0.012481657900000002,
      "max": 0.021307921900000002
    }
  ]
}

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

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 2.682 ± 0.087 2.564 2.818 1.68 ± 0.13
pacquet@main 2.741 ± 0.105 2.598 2.933 1.72 ± 0.14
pnpr@HEAD 1.595 ± 0.115 1.467 1.876 1.00
pnpr@main 1.767 ± 0.093 1.641 1.896 1.11 ± 0.10
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 2.68151949014,
      "stddev": 0.08661734346315392,
      "median": 2.6902136895399997,
      "user": 2.3978456599999998,
      "system": 1.92280972,
      "min": 2.56350364504,
      "max": 2.81810348604
    },
    {
      "command": "pacquet@main",
      "mean": 2.7410159411399997,
      "stddev": 0.10504518647225156,
      "median": 2.7578099740399997,
      "user": 2.6860465600000003,
      "system": 2.0182102199999994,
      "min": 2.59837336604,
      "max": 2.93298869104
    },
    {
      "command": "pnpr@HEAD",
      "mean": 1.59492896864,
      "stddev": 0.11465473895989106,
      "median": 1.55756745954,
      "user": 1.5867414599999998,
      "system": 1.6007975199999998,
      "min": 1.4672694320400002,
      "max": 1.8762052670400002
    },
    {
      "command": "pnpr@main",
      "mean": 1.76685689964,
      "stddev": 0.09270546276775526,
      "median": 1.79340604704,
      "user": 1.8121738600000001,
      "system": 1.9473538199999996,
      "min": 1.6413280190400001,
      "max": 1.8955774600400002
    }
  ]
}

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

Command Mean [ms] Min [ms] Max [ms] Relative
pacquet@HEAD 860.5 ± 139.9 695.5 1070.7 1.98 ± 0.33
pacquet@main 940.8 ± 79.4 831.2 1069.5 2.16 ± 0.21
pnpr@HEAD 514.5 ± 38.8 473.0 585.8 1.18 ± 0.10
pnpr@main 435.1 ± 19.2 399.2 474.2 1.00

Confirmation (reversed target order)

Command Mean [ms] Min [ms] Max [ms] Relative
pnpr@main 448.4 ± 13.5 424.3 465.6 1.02 ± 0.08
pnpr@HEAD 439.1 ± 29.8 375.7 479.7 1.00
pacquet@main 851.9 ± 76.7 736.3 1015.0 1.94 ± 0.22
pacquet@HEAD 838.9 ± 54.0 800.6 970.4 1.91 ± 0.18
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 0.86049864414,
      "stddev": 0.13985207753987278,
      "median": 0.8116109368400001,
      "user": 1.2490173199999999,
      "system": 1.1799193799999999,
      "min": 0.6955043763400001,
      "max": 1.07072889234
    },
    {
      "command": "pacquet@main",
      "mean": 0.9407711393400001,
      "stddev": 0.07944394762447222,
      "median": 0.9270608788400001,
      "user": 1.3433591199999997,
      "system": 1.3237859799999998,
      "min": 0.8311616153400001,
      "max": 1.06946099734
    },
    {
      "command": "pnpr@HEAD",
      "mean": 0.5145157799400001,
      "stddev": 0.03878055781254938,
      "median": 0.49794497634,
      "user": 0.5278888199999999,
      "system": 0.9050731799999999,
      "min": 0.47298002534000005,
      "max": 0.5858444163400001
    },
    {
      "command": "pnpr@main",
      "mean": 0.43512407844000006,
      "stddev": 0.01922190381326951,
      "median": 0.43543564534,
      "user": 0.42985342,
      "system": 0.7981237800000001,
      "min": 0.3992323863400001,
      "max": 0.4741599443400001
    }
  ],
  "confirmation": {
    "results": [
      {
        "command": "pnpr@main",
        "mean": 0.44840504890000005,
        "stddev": 0.013515615750907739,
        "median": 0.4472053507,
        "user": 0.41358754,
        "system": 0.8563954599999999,
        "min": 0.4242549922,
        "max": 0.46555976320000003
      },
      {
        "command": "pnpr@HEAD",
        "mean": 0.43913928659999996,
        "stddev": 0.02975526212696939,
        "median": 0.4424297962,
        "user": 0.4381093399999999,
        "system": 0.7997845599999999,
        "min": 0.3757288992,
        "max": 0.4796881692
      },
      {
        "command": "pacquet@main",
        "mean": 0.8519231347000001,
        "stddev": 0.07666480714081769,
        "median": 0.8288284367000001,
        "user": 1.20981924,
        "system": 1.19719586,
        "min": 0.7363042002000001,
        "max": 1.0150319022
      },
      {
        "command": "pacquet@HEAD",
        "mean": 0.8388991438000002,
        "stddev": 0.05399250226527655,
        "median": 0.8198058172,
        "user": 1.2196888399999997,
        "system": 1.1774732600000002,
        "min": 0.8006252422000001,
        "max": 0.9703991432000001
      }
    ]
  }
}

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

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 2.040 ± 0.113 1.916 2.262 4.67 ± 0.45
pacquet@main 2.068 ± 0.051 1.980 2.159 4.74 ± 0.39
pnpr@HEAD 0.437 ± 0.034 0.394 0.495 1.00
pnpr@main 0.480 ± 0.021 0.447 0.510 1.10 ± 0.10
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 2.03961946678,
      "stddev": 0.11329699586770871,
      "median": 2.00161730038,
      "user": 1.59034836,
      "system": 1.5207397999999999,
      "min": 1.91553017188,
      "max": 2.26224771288
    },
    {
      "command": "pacquet@main",
      "mean": 2.06779276248,
      "stddev": 0.05100717862348642,
      "median": 2.06883693038,
      "user": 1.6027347600000001,
      "system": 1.5507034999999998,
      "min": 1.98024542988,
      "max": 2.1591072588799998
    },
    {
      "command": "pnpr@HEAD",
      "mean": 0.43651016998000003,
      "stddev": 0.0339931876345218,
      "median": 0.42773429738,
      "user": 0.41637806,
      "system": 0.9029425000000002,
      "min": 0.39428612888,
      "max": 0.49461479688
    },
    {
      "command": "pnpr@main",
      "mean": 0.47968209268000006,
      "stddev": 0.020568081758240403,
      "median": 0.48166645238000005,
      "user": 0.46690666,
      "system": 0.9302247999999999,
      "min": 0.44720739588,
      "max": 0.50976449288
    }
  ]
}

Scenario: Isolated linker: fresh resolve, hot cache, offline

Command Mean [ms] Min [ms] Max [ms] Relative
pacquet@HEAD 313.2 ± 28.7 279.1 358.3 2.75 ± 0.31
pacquet@main 327.8 ± 37.1 264.0 385.8 2.88 ± 0.38
pnpr@HEAD 113.7 ± 7.6 97.2 127.4 1.00
pnpr@main 123.8 ± 7.6 110.2 138.2 1.09 ± 0.10
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 0.31324830914,
      "stddev": 0.028723274103936002,
      "median": 0.30229782684000006,
      "user": 0.3090987399999999,
      "system": 0.1404082,
      "min": 0.27905042184,
      "max": 0.35831388484000004
    },
    {
      "command": "pacquet@main",
      "mean": 0.32784208474000004,
      "stddev": 0.03705960832285307,
      "median": 0.33007714734000004,
      "user": 0.32556613999999995,
      "system": 0.15041219999999997,
      "min": 0.26399274884,
      "max": 0.38583440084000004
    },
    {
      "command": "pnpr@HEAD",
      "mean": 0.11371739453565217,
      "stddev": 0.0076161424319804456,
      "median": 0.11402791684000001,
      "user": 0.05041284434782609,
      "system": 0.02300598260869565,
      "min": 0.09719921384,
      "max": 0.12741329484
    },
    {
      "command": "pnpr@main",
      "mean": 0.12381823703047619,
      "stddev": 0.007570397698565201,
      "median": 0.12254460584000001,
      "user": 0.05728434952380952,
      "system": 0.027596342857142853,
      "min": 0.11024399484000001,
      "max": 0.13821848684000002
    }
  ]
}

Scenario: Isolated linker: peer-heavy resolve, hot cache, offline

Command Mean [ms] Min [ms] Max [ms] Relative
pacquet@HEAD 325.7 ± 38.0 289.1 399.7 1.11 ± 0.20
pacquet@main 293.5 ± 41.4 250.5 375.0 1.00
pnpm@HEAD 4571.9 ± 297.5 4066.4 5159.3 15.58 ± 2.42
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 0.3256610305222223,
      "stddev": 0.037964270358299725,
      "median": 0.31012074030000003,
      "user": 0.34726703555555555,
      "system": 0.07501299111111111,
      "min": 0.28910716430000005,
      "max": 0.3997495823000001
    },
    {
      "command": "pacquet@main",
      "mean": 0.2934884658555556,
      "stddev": 0.04136907103798834,
      "median": 0.2768445493,
      "user": 0.31485725777777784,
      "system": 0.07211665777777777,
      "min": 0.25053615530000006,
      "max": 0.3750065443
    },
    {
      "command": "pnpm@HEAD",
      "mean": 4.571887614855555,
      "stddev": 0.29745631558451985,
      "median": 4.5787718613,
      "user": 7.3703751466666665,
      "system": 0.4480034355555555,
      "min": 4.0664488343,
      "max": 5.1592603563
    }
  ]
}

Scenario: Isolated linker: linked-workspace resolve, hot cache, offline

Command Mean [ms] Min [ms] Max [ms] Relative
pacquet@HEAD 257.4 ± 17.5 230.7 292.2 1.03 ± 0.09
pacquet@main 249.5 ± 14.7 232.7 270.5 1.00
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 0.2574032541545454,
      "stddev": 0.01753612407177372,
      "median": 0.2602130487,
      "user": 0.21462687272727274,
      "system": 0.09864746909090907,
      "min": 0.2307332387,
      "max": 0.2921561187
    },
    {
      "command": "pacquet@main",
      "mean": 0.24946024260909094,
      "stddev": 0.014676297966604836,
      "median": 0.24266234370000003,
      "user": 0.18770950909090905,
      "system": 0.11574292363636363,
      "min": 0.23265458870000003,
      "max": 0.27045687370000004
    }
  ]
}

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

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 4.594 ± 0.182 4.396 4.905 1.27 ± 0.06
pacquet@main 4.769 ± 0.284 4.478 5.330 1.32 ± 0.09
pnpr@HEAD 3.604 ± 0.100 3.445 3.716 1.00
pnpr@main 3.617 ± 0.134 3.464 3.845 1.00 ± 0.05
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 4.5939439570200005,
      "stddev": 0.18189866906611424,
      "median": 4.5269532983200005,
      "user": 1.76113982,
      "system": 1.70205982,
      "min": 4.39553735332,
      "max": 4.90539829532
    },
    {
      "command": "pacquet@main",
      "mean": 4.768916291720001,
      "stddev": 0.2844395351422548,
      "median": 4.694843626320001,
      "user": 2.09645082,
      "system": 1.87125652,
      "min": 4.4779034633200006,
      "max": 5.32993937432
    },
    {
      "command": "pnpr@HEAD",
      "mean": 3.6044115333199995,
      "stddev": 0.10032250519621434,
      "median": 3.6469709243199997,
      "user": 1.7432073199999998,
      "system": 1.6979422199999998,
      "min": 3.4449748373199998,
      "max": 3.71566273332
    },
    {
      "command": "pnpr@main",
      "mean": 3.6165626867199996,
      "stddev": 0.13404994504730652,
      "median": 3.57194835482,
      "user": 1.6264912200000006,
      "system": 1.4801732199999997,
      "min": 3.46363041532,
      "max": 3.84502972232
    }
  ]
}

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

Same install as the isolated fresh-restore hot/hot scenario, into the shared virtual store, the layout pnpm 12 installs into by default. Scenarios run on separate machines, so compare main and branch within each table.

Command Mean [ms] Min [ms] Max [ms] Relative
pacquet@HEAD 482.8 ± 47.8 425.3 553.9 2.14 ± 0.34
pacquet@main 428.4 ± 48.9 368.0 509.9 1.90 ± 0.32
pnpr@HEAD 326.4 ± 130.0 224.7 578.4 1.45 ± 0.61
pnpr@main 225.1 ± 28.2 180.5 268.7 1.00
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 0.48275442150000003,
      "stddev": 0.04782596309557285,
      "median": 0.48391411070000007,
      "user": 0.34209082,
      "system": 0.22161408,
      "min": 0.42531569820000004,
      "max": 0.5539115422
    },
    {
      "command": "pacquet@main",
      "mean": 0.42836263100000005,
      "stddev": 0.048915631513759765,
      "median": 0.42204262620000005,
      "user": 0.31695552,
      "system": 0.20878917999999996,
      "min": 0.36800009820000007,
      "max": 0.5098966332
    },
    {
      "command": "pnpr@HEAD",
      "mean": 0.32635574370000003,
      "stddev": 0.12999073563401028,
      "median": 0.25652227020000007,
      "user": 0.31176212000000003,
      "system": 0.23594278000000002,
      "min": 0.2247049392,
      "max": 0.5783667312
    },
    {
      "command": "pnpr@main",
      "mean": 0.22511260279999998,
      "stddev": 0.028212916879756363,
      "median": 0.22176917770000001,
      "user": 0.28125962000000004,
      "system": 0.21424657999999996,
      "min": 0.1805084542,
      "max": 0.26870691720000006
    }
  ]
}

@tunglambk
tunglambk force-pushed the fix/reject-invalid-dependency-specifiers branch from 1009af9 to e000b4a Compare September 30, 2026 03:20
@tunglambk
tunglambk marked this pull request as ready for review September 30, 2026 03:32
@tunglambk
tunglambk requested a review from zkochan as a code owner September 30, 2026 03:32
Copilot AI balanced review requested due to automatic review settings September 30, 2026 03:32
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions github-actions Bot added the reviewed: coderabbit CodeRabbit submitted an approving review label Sep 30, 2026
Copilot AI balanced review requested due to automatic review settings October 5, 2026 02:50
@tunglambk
tunglambk force-pushed the fix/reject-invalid-dependency-specifiers branch from e000b4a to efb28ac Compare October 5, 2026 02:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@tunglambk
tunglambk force-pushed the fix/reject-invalid-dependency-specifiers branch from efb28ac to 3c79530 Compare October 5, 2026 03:07
Copilot AI balanced review requested due to automatic review settings October 5, 2026 03:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

…tion

rustfmt wants `scope.project_manifests` on the same line as the `&&`. This is
`node pnpm/scripts/rustfmt.mjs --all` on its own; the token stream is unchanged.
Copilot AI balanced review requested due to automatic review settings October 5, 2026 15:32
@zkochan
zkochan force-pushed the fix/reject-invalid-dependency-specifiers branch from e5c502f to fd788c1 Compare October 5, 2026 15:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@greptile-apps greptile-apps Bot 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.

Your trial has ended. Reactivate Greptile to resume code reviews.

pnpm v11 installs a project whose dependency group is null, an array, or
a string, and treats it as declaring no dependencies. Only a non-string
specifier inside an object group makes it fail. Rejecting the group shapes
would have broken projects with, for example, "devDependencies": null.

Restrict validation to specifiers, replace the resolved TODOs on
PackageManifest::dependencies with its contract, and reword the changeset
around the user-visible change.
@qodo-code-review

qodo-code-review Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Informational

1. Dependency metadata maps can still carry malformed values 🐞 Bug ≡ Correctness
Description
validate_dependency_types only checks the four DependencyGroup fields (dependencies,
devDependencies, optionalDependencies, peerDependencies) for non-string specifiers, leaving
sibling fields like dependenciesMeta and peerDependenciesMeta — which are also keyed objects
consumed later by the dependency graph and optimistic-repeat-install code — entirely unvalidated. A
manifest with a malformed dependenciesMeta entry (e.g. a non-object value) still passes this new
validation gate and reaches downstream consumers that assume a well-shaped object, reintroducing the
same class of silently-accepted malformed manifest this PR is meant to close, just for a different
field.
Code

pnpm/crates/package-manifest/src/validation.rs[R8-13]

+        for group in [
+            DependencyGroup::Prod,
+            DependencyGroup::Dev,
+            DependencyGroup::Optional,
+            DependencyGroup::Peer,
+        ] {
Relevance

●● Moderate

Manifest-shape correctness concerns are accepted historically, but no close precedent covers
validating metadata fields.

PR-#15153
PR-#13157

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The loop only iterates DependencyGroup::{Prod, Dev, Optional, Peer}, which map to
dependencies/devDependencies/optionalDependencies/peerDependencies. dependenciesMeta and
peerDependenciesMeta are separate manifest fields read elsewhere in the package-manager (e.g.
dependencies_graph_to_lockfile/importers.rs and optimistic_repeat_install.rs) and are not part of
DependencyGroup, so they bypass this new check entirely.

pnpm/crates/package-manifest/src/validation.rs[8-21]
pnpm/crates/package-manifest/src/lib.rs[41-46]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`validate_dependency_types` only validates the four core dependency groups and ignores `dependenciesMeta` and `peerDependenciesMeta`, which are also attacker/user-controlled keyed maps consumed later in the install pipeline and can similarly be malformed without detection.

## Fix Focus Areas
- pnpm/crates/package-manifest/src/validation.rs[8-21]

## Recommended Fix
Add shape validation for `dependenciesMeta` and `peerDependenciesMeta` (each entry should be an object with the expected meta keys) alongside the existing dependency-group checks, returning the same `PackageManifestError::InvalidAttribute` so malformed entries in these fields are rejected consistently with the rest of this PR's validation.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Dismiss ↗ | View ↗


2. No-op installs now scan every dependency on the hot path 🐞 Bug ➹ Performance
Description
malformed_declaration_blocks_repeat_install is invoked unconditionally inside
repeat_install_verdict for any non-NoInstall mutation, iterating every project manifest's four
dependency maps with validate_dependency_types() before the existing repeat-install shortcut can
return Unchanged. This adds an O(total dependency entries) scan to every ordinary pnpm install
invocation — including the common, fully up-to-date case the fast path exists to make cheap —
proportional to workspace size rather than to whether anything actually changed.
Code

pnpm/crates/package-manager/src/install/run/fast_path.rs[R243-244]

        if changed
+            || self.malformed_declaration_blocks_repeat_install(scope)
Relevance

● Weak

Recent fast-path performance precedent rejected added pre-shortcut discovery cost when semantics
required the check.

PR-#16247

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
fast_path.rs adds || self.malformed_declaration_blocks_repeat_install(scope) into the condition
that decides whether the repeat-install shortcut is abandoned. manifest_validation.rs's
malformed_declaration_blocks_repeat_install runs `scope.project_manifests.iter().any(|(_,
manifest)| manifest.validate_dependency_types().is_err())`, scanning every manifest and every
dependency entry in all four groups, for every install where `self.install.execution.mutation !=
ProjectMutation::NoInstall` — which covers the normal install path, not just malformed-manifest
cases.

pnpm/crates/package-manager/src/install/run/fast_path.rs[241-247]
pnpm/crates/package-manager/src/install/run/manifest_validation.rs[13-21]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new `malformed_declaration_blocks_repeat_install` check runs unconditionally on every non-`NoInstall` install, scanning all project manifests' dependency maps even when the pre-existing repeat-install logic already determined nothing changed, adding avoidable work to the fast path that exists specifically to make no-op installs cheap.

## Fix Focus Areas
- pnpm/crates/package-manager/src/install/run/fast_path.rs[241-247]
- pnpm/crates/package-manager/src/install/run/manifest_validation.rs[13-21]

## Recommended Fix
Only perform the malformed-declaration scan when it can actually change the outcome — e.g. restructure the condition so the validation scan short-circuits as soon as a prior check (`changed`) is true, and consider caching or memoizing the validation result per manifest (e.g. keyed by manifest content hash) so it isn't recomputed on every install invocation when manifests haven't changed since the last run.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Dismiss ↗ | View ↗


Grey Divider

Context sources
✅ Compliance rules (platform): 66 rules
✅ Cross-repo context — repo relationships
  Explored: repo: pnpm/update (sha: fcaa952e) — View relationship
  Explored: repo: pnpm/setup (sha: fbda4c85) — View relationship
  Explored: repo: pnpm/exec (sha: d9dca8d2) — View relationship
Review mode: Auto: 🚀 Fast: Localized validation behavior change with contained install impact

Grey Divider

Tip of the day
💡 Did you know, you can turn on the rule miner and Qodo learns your standards from review history

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Previous reviews

Review updated until commit 1da1604

Results up to commit fd788c1 ⚖️ Balanced


🐞 Bugs (2) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)


Informational
1. Dependency metadata maps can still carry malformed values 🐞 Bug ≡ Correctness
Description
validate_dependency_types only checks the four DependencyGroup fields (dependencies,
devDependencies, optionalDependencies, peerDependencies) for non-string specifiers, leaving
sibling fields like dependenciesMeta and peerDependenciesMeta — which are also keyed objects
consumed later by the dependency graph and optimistic-repeat-install code — entirely unvalidated. A
manifest with a malformed dependenciesMeta entry (e.g. a non-object value) still passes this new
validation gate and reaches downstream consumers that assume a well-shaped object, reintroducing the
same class of silently-accepted malformed manifest this PR is meant to close, just for a different
field.
Code

pnpm/crates/package-manifest/src/validation.rs[R8-13]

+        for group in [
+            DependencyGroup::Prod,
+            DependencyGroup::Dev,
+            DependencyGroup::Optional,
+            DependencyGroup::Peer,
+        ] {
Relevance

●● Moderate

Manifest-shape correctness concerns are accepted historically, but no close precedent covers
validating metadata fields.

PR-#15153
PR-#13157

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The loop only iterates DependencyGroup::{Prod, Dev, Optional, Peer}, which map to
dependencies/devDependencies/optionalDependencies/peerDependencies. dependenciesMeta and
peerDependenciesMeta are separate manifest fields read elsewhere in the package-manager (e.g.
dependencies_graph_to_lockfile/importers.rs and optimistic_repeat_install.rs) and are not part of
DependencyGroup, so they bypass this new check entirely.

pnpm/crates/package-manifest/src/validation.rs[8-21]
pnpm/crates/package-manifest/src/lib.rs[41-46]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`validate_dependency_types` only validates the four core dependency groups and ignores `dependenciesMeta` and `peerDependenciesMeta`, which are also attacker/user-controlled keyed maps consumed later in the install pipeline and can similarly be malformed without detection.

## Fix Focus Areas
- pnpm/crates/package-manifest/src/validation.rs[8-21]

## Recommended Fix
Add shape validation for `dependenciesMeta` and `peerDependenciesMeta` (each entry should be an object with the expected meta keys) alongside the existing dependency-group checks, returning the same `PackageManifestError::InvalidAttribute` so malformed entries in these fields are rejected consistently with the rest of this PR's validation.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Dismiss ↗ | View ↗


2. No-op installs now scan every dependency on the hot path 🐞 Bug ➹ Performance
Description
malformed_declaration_blocks_repeat_install is invoked unconditionally inside
repeat_install_verdict for any non-NoInstall mutation, iterating every project manifest's four
dependency maps with validate_dependency_types() before the existing repeat-install shortcut can
return Unchanged. This adds an O(total dependency entries) scan to every ordinary pnpm install
invocation — including the common, fully up-to-date case the fast path exists to make cheap —
proportional to workspace size rather than to whether anything actually changed.
Code

pnpm/crates/package-manager/src/install/run/fast_path.rs[R243-244]

        if changed
+            || self.malformed_declaration_blocks_repeat_install(scope)
Relevance

● Weak

Recent fast-path performance precedent rejected added pre-shortcut discovery cost when semantics
required the check.

PR-#16247

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
fast_path.rs adds || self.malformed_declaration_blocks_repeat_install(scope) into the condition
that decides whether the repeat-install shortcut is abandoned. manifest_validation.rs's
malformed_declaration_blocks_repeat_install runs `scope.project_manifests.iter().any(|(_,
manifest)| manifest.validate_dependency_types().is_err())`, scanning every manifest and every
dependency entry in all four groups, for every install where `self.install.execution.mutation !=
ProjectMutation::NoInstall` — which covers the normal install path, not just malformed-manifest
cases.

pnpm/crates/package-manager/src/install/run/fast_path.rs[241-247]
pnpm/crates/package-manager/src/install/run/manifest_validation.rs[13-21]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new `malformed_declaration_blocks_repeat_install` check runs unconditionally on every non-`NoInstall` install, scanning all project manifests' dependency maps even when the pre-existing repeat-install logic already determined nothing changed, adding avoidable work to the fast path that exists specifically to make no-op installs cheap.

## Fix Focus Areas
- pnpm/crates/package-manager/src/install/run/fast_path.rs[241-247]
- pnpm/crates/package-manager/src/install/run/manifest_validation.rs[13-21]

## Recommended Fix
Only perform the malformed-declaration scan when it can actually change the outcome — e.g. restructure the condition so the validation scan short-circuits as soon as a prior check (`changed`) is true, and consider caching or memoizing the validation result per manifest (e.g. keyed by manifest content hash) so it isn't recomputed on every install invocation when manifests haven't changed since the last run.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Dismiss ↗ | View ↗


Grey Divider

Qodo Logo

Copilot AI balanced review requested due to automatic review settings October 5, 2026 16:01
@qodo-code-review

Copy link
Copy Markdown

PR Code Suggestions ✨

Warning

/improve is deprecated. Use /agentic_review instead (removal date not yet scheduled).

No code suggestions found for the PR.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@greptile-apps greptile-apps Bot 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.

Your trial has ended. Reactivate Greptile to resume code reviews.

@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 1da1604

@qodo-code-review

Copy link
Copy Markdown

PR Code Suggestions ✨

Warning

/improve is deprecated. Use /agentic_review instead (removal date not yet scheduled).

No code suggestions found for the PR.

@zkochan

zkochan commented Oct 5, 2026

Copy link
Copy Markdown
Member

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

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.

@zkochan

zkochan commented Oct 5, 2026

Copy link
Copy Markdown
Member

Review-and-fix pass on head 1da1604461.

Fix pushed (1da1604461): the validation rejected dependency groups that are not objects (null, [], a string). pnpm v11 accepts those as declaring no dependencies and fails only on a non-string specifier, so v12 would have newly broken projects with, for example, "devDependencies": null. I checked this against the v11 bundle for all four groups. Validation now checks only specifiers inside object groups. I also replaced the resolved TODOs on PackageManifest::dependencies with its contract and reworded the changeset. The branch was rebased onto main first.

Qodo findings, verified and declined:

  1. dependenciesMeta / peerDependenciesMeta are not validated. These fields declare no dependencies, so a malformed value cannot silently drop a dependency from the lockfile, which is the bug this PR fixes. Out of scope.
  2. The repeat-install scan on the hot path. It is a type check over JSON that is already parsed. The repeat-install verdict already walks the same dependency maps, and the same-run benchmark shows the isolated-repeat-install-* scenarios within tolerance.

Status: all checks green. CodeRabbit reviewed 1da1604461 with no actionable comments. Copilot (quota) and Greptile (trial ended) did not review. The PR stays ready for review. Nothing else remains within scope.


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

@zkochan
zkochan merged commit 1a3a3b4 into pnpm:main Oct 5, 2026
56 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

product: pacquet reviewed: coderabbit CodeRabbit submitted an approving review state: automerge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants