fix(pacquet): require pnpm-lock.yaml for single-project optimisticRepeatInstall fast path#11945
Conversation
…eatInstall fast path The port of pnpm's `optimisticRepeatInstall` short-circuit in #11943 applied the workspace branch's mtime-only exit (`checkDepsStatus.ts:263-271`) to every install, including single-project ones. Pnpm's single-project branch (`checkDepsStatus.ts:387-462`) additionally throws `RUN_CHECK_DEPS_LOCKFILE_NOT_FOUND` when `pnpm-lock.yaml` is absent, which the outer `try` converts into `upToDate: false`. Without that gate, pacquet treated a single-project install with `node_modules` present but no lockfile as "Already up to date" — the pnpm.io `node_modules`-only and `cache+node_modules` benchmark cells finished in ~35 ms instead of running the install (pnpm ~5–7 s on the same fixtures). Add an `is_workspace_install: bool` parameter; in single-project mode, require `<workspace_root>/pnpm-lock.yaml` to exist before declaring the install up to date. Workspace installs continue to skip the lockfile probe — pnpm's workspace branch's only lockfile check (`findConflictedLockfileDir`) silently `continue`s on ENOENT (`checkDepsStatus.ts:593-596`). Tests: - `returns_skipped_when_lockfile_missing_in_single_project_mode` - `returns_up_to_date_in_workspace_mode_without_lockfile` - `optimistic_repeat_install_does_not_short_circuit_when_lockfile_missing` (install-level integration test) - Existing happy-path tests now seed `pnpm-lock.yaml` via a new `write_empty_lockfile` helper in `setup_fresh_install`.
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
📜 Recent review details⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
🧰 Additional context used📓 Path-based instructions (1)pacquet/**/*.rs📄 CodeRabbit inference engine (pacquet/AGENTS.md)
Files:
🧠 Learnings (3)📚 Learning: 2026-05-20T19:40:55.051ZApplied to files:
📚 Learning: 2026-05-22T00:08:44.646ZApplied to files:
📚 Learning: 2026-05-20T23:07:58.444ZApplied to files:
🧬 Code graph analysis (1)pacquet/crates/package-manager/src/install/tests.rs (6)
🔇 Additional comments (5)
📝 WalkthroughWalkthroughThe PR updates the optimistic repeat-install fast-path to require ChangesOptimistic repeat-install lockfile gating
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
Micro-Benchmark ResultsLinux |
…ps end-to-end Add `optimistic_repeat_install_round_trips_on_single_project_install`: two real `Install::run` calls back-to-back on a non-workspace project (no `pnpm-workspace.yaml`). The first install resolves through the registry mock and writes `pnpm-lock.yaml` + `.pnpm-workspace-state-v1.json` to disk. The second install must hit the optimistic fast path — emit `Already up to date` and skip every install-setup event. Pairs with the negative `..._does_not_short_circuit_when_lockfile_missing` test so the gate's polarity is pinned in both directions.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #11945 +/- ##
=======================================
Coverage 87.91% 87.92%
=======================================
Files 227 227
Lines 27736 27741 +5
=======================================
+ Hits 24385 24392 +7
+ Misses 3351 3349 -2 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Integrated-Benchmark Report (Linux)Scenario: Isolated linker: fresh restore, cold cache + cold store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 1.6410764912600002,
"stddev": 0.0529087904971523,
"median": 1.6365262883600002,
"user": 2.9540500399999994,
"system": 2.01717498,
"min": 1.58555848036,
"max": 1.7536920923600001,
"times": [
1.64416442036,
1.6288881563600002,
1.7536920923600001,
1.69499885436,
1.58885214636,
1.58679493236,
1.6457557153600002,
1.65930649736,
1.58555848036,
1.6227536173600001
]
},
{
"command": "pacquet@main",
"mean": 1.6260175660599998,
"stddev": 0.0672404621317466,
"median": 1.59904334786,
"user": 2.96137654,
"system": 2.00993658,
"min": 1.5547546923600002,
"max": 1.7453371473600001,
"times": [
1.7453371473600001,
1.58570122336,
1.61238547236,
1.55863839136,
1.5756714203600002,
1.6537663533600002,
1.5547546923600002,
1.68390367836,
1.7068467763600002,
1.58317050536
]
}
]
}Scenario: Isolated linker: fresh restore, hot cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 0.46914293468000007,
"stddev": 0.03256504224259272,
"median": 0.45828411828000004,
"user": 0.3534346599999999,
"system": 0.79282758,
"min": 0.45399295928000005,
"max": 0.56129072528,
"times": [
0.56129072528,
0.4675697092800001,
0.45805426928000004,
0.45953550228000006,
0.45677328128000005,
0.45809950628000007,
0.4574204652800001,
0.46022419828000005,
0.45846873028000007,
0.45399295928000005
]
},
{
"command": "pacquet@main",
"mean": 0.46905583778000004,
"stddev": 0.012928301049420811,
"median": 0.46758836278000004,
"user": 0.36024836000000005,
"system": 0.8056173799999999,
"min": 0.45683918128000006,
"max": 0.50281538628,
"times": [
0.50281538628,
0.47140991028000007,
0.45683918128000006,
0.46944615128000006,
0.47037554328000003,
0.46032885428000003,
0.46573057428000003,
0.46119457628000005,
0.46159096228000007,
0.47082723828000006
]
}
]
}Scenario: Isolated linker: fresh install, cold cache + cold store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 2.0281953272399997,
"stddev": 0.040319029683009946,
"median": 2.03323616994,
"user": 4.12807276,
"system": 1.9250203200000002,
"min": 1.94677477044,
"max": 2.08250102044,
"times": [
2.0259483934399998,
1.94677477044,
2.0707279814399997,
2.04162900644,
2.0201193424399997,
2.0494735134399997,
2.08250102044,
2.0332872964399997,
1.9783069044400001,
2.03318504344
]
},
{
"command": "pacquet@main",
"mean": 2.00115579704,
"stddev": 0.021801412745734473,
"median": 1.9954047969400002,
"user": 4.117628259999999,
"system": 1.91679252,
"min": 1.9773600804400002,
"max": 2.03423497344,
"times": [
1.9943991394400002,
1.98240257244,
1.9773600804400002,
2.03423497344,
1.98570844044,
2.02520063844,
1.99641045444,
1.9800756974400002,
2.00415985644,
2.03160611744
]
}
]
}Scenario: Isolated linker: fresh install, hot cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 1.20758995934,
"stddev": 0.009093811469568783,
"median": 1.2078996158399997,
"user": 1.68685094,
"system": 1.12605804,
"min": 1.19563411134,
"max": 1.22253631634,
"times": [
1.2106664323399998,
1.22253631634,
1.21156526734,
1.21709104634,
1.19563411134,
1.20237229434,
1.19744075334,
1.19883547034,
1.2146251023399999,
1.2051327993399998
]
},
{
"command": "pacquet@main",
"mean": 1.2246726167400002,
"stddev": 0.021344065275708757,
"median": 1.22050435984,
"user": 1.6917739400000003,
"system": 1.14171454,
"min": 1.19527679634,
"max": 1.25837654334,
"times": [
1.21080626434,
1.20106411534,
1.21574948034,
1.23029745934,
1.2569529983399998,
1.2221781783399999,
1.19527679634,
1.21883054134,
1.25837654334,
1.2371937903399999
]
}
]
} |
|
| Branch | pr/11945 |
| Testbed | pacquet |
Click to view all benchmark results
| Benchmark | Latency | Benchmark Result milliseconds (ms) (Result Δ%) | Upper Boundary milliseconds (ms) (Limit %) |
|---|---|---|---|
| isolated-linker.fresh-install.cold-cache.cold-store | 📈 view plot 🚷 view threshold | 2,028.20 ms(-42.18%)Baseline: 3,507.67 ms | 4,209.21 ms (48.18%) |
| isolated-linker.fresh-install.hot-cache.hot-store | 📈 view plot 🚷 view threshold | 1,207.59 ms(-53.06%)Baseline: 2,572.56 ms | 3,087.07 ms (39.12%) |
| isolated-linker.fresh-restore.cold-cache.cold-store | 📈 view plot 🚷 view threshold | 1,641.08 ms(-24.10%)Baseline: 2,162.27 ms | 2,594.72 ms (63.25%) |
| isolated-linker.fresh-restore.hot-cache.hot-store | 📈 view plot 🚷 view threshold | 469.14 ms(-28.50%)Baseline: 656.12 ms | 787.35 ms (59.59%) |
Summary
Follow-up to #11943. The
optimisticRepeatInstallport applied pnpm's workspace-branch mtime-only exit to every install, including single-project ones. Pnpm's single-project branch additionally throwsRUN_CHECK_DEPS_LOCKFILE_NOT_FOUNDwhenpnpm-lock.yamlis missing — the outertryconverts that toupToDate: false. Pacquet skipped that gate, so a single-project install withnode_modulespresent but no lockfile silently short-circuited as "Already up to date" in ~35 ms (vs pnpm running the real install at ~5–7 s on the same fixture).This was visible in the
pnpm.iobenchmarks the day #11943 landed:Both rows have no lockfile but pacquet was hitting the optimistic fast path anyway.
Fix
is_workspace_install: boolparameter tocheck_optimistic_repeat_install.pnpm-workspace.yaml), require<workspace_root>/pnpm-lock.yamlto exist before returningUpToDate.findConflictedLockfileDir) silentlycontinues on ENOENT (checkDepsStatus.ts:593-596), so pacquet matches that polarity.Caller in
install.rspassesworkspace_manifest.is_some().Tests ported / added
returns_skipped_when_lockfile_missing_in_single_project_mode— unit test; the direct regression catcher.returns_up_to_date_in_workspace_mode_without_lockfile— unit test; proves the workspace branch still tolerates a missing lockfile, matching pnpm.optimistic_repeat_install_does_not_short_circuit_when_lockfile_missing— install-level integration test; observes the absence of theAlready up to datelog whenpnpm-lock.yamlis missing.pnpm-lock.yamlvia a newwrite_empty_lockfilehelper insidesetup_fresh_installso they exercise the realistic single-project layout.Verified per pacquet conventions by temporarily disabling the new gate — both new tests fail; with the gate restored, all 332
pacquet-package-managertests pass.just fmt/check/lintare clean.Test plan
cargo nextest run -p pacquet-package-manager optimistic_repeat(16 tests, all pass)cargo nextest run -p pacquet-package-managerfull crate (332 pass)just fmt,just check,just lintWritten by an agent (Claude Code, claude-opus-4-7).
Summary by CodeRabbit
Release Notes
Bug Fixes
Tests