Skip to content

fix(pacquet/config): pick the store on the project's volume#11804

Merged
zkochan merged 4 commits into
mainfrom
pacquet-store-dir-cross-volume
May 21, 2026
Merged

fix(pacquet/config): pick the store on the project's volume#11804
zkochan merged 4 commits into
mainfrom
pacquet-store-dir-cross-volume

Conversation

@zkochan

@zkochan zkochan commented May 21, 2026

Copy link
Copy Markdown
Member

Summary

  • Port pnpm's getStorePath / storePathRelativeToHome cross-volume detection to pacquet's default_store_dir.
  • When no storeDir is explicitly set, Config::current now probes whether the project root can be hardlinked into the user's pnpm home dir and falls back to <mountpoint>/.pnpm-store when it can't.
  • New LinkProbe capability in pacquet-config::api so tests can answer the linkability question deterministically; production Host impl performs the real link attempts.

Why

Before this fix, a workspace on a separate volume (e.g. /Volumes/src/ on macOS) installed into the home-volume store (~/Library/pnpm/store). When the home volume is case-insensitive and the workspace volume is case-sensitive, typescript-eslint's path cache canonicalises against the home store and then can't locate the same files in TypeScript's case-sensitive program loaded from the workspace, so eslint --fix fails with Parsing error: ESLint was configured to run on <file> using parserOptions.project: tsconfig.lint.json — TSConfig does not include this file on every project file.

Concrete repro: on the use-pacquet-config-dep branch (which delegates installs to pacquet via configDependencies['@pnpm/pacquet']), pn -w compile fails its eslint --fix step with ~50 of those errors. After this fix pacquet picks /Volumes/src/.pnpm-store/v11 — the same store pnpm 11 picks via getStorePath — and the lint succeeds.

What landed

  • pacquet/crates/config/src/store_path.rs — port of getStorePath / storePathRelativeToHome / rootLinkTarget / canLinkToSubdir / nextPath. Algorithm is generic over LinkProbe; the Host impl performs the real link attempts via host_can_link_between_dirs.
  • pacquet/crates/config/src/store_path/tests.rs — 11 unit tests: next_path walks, filesystem_root extraction, same-volume happy path (real fixture), cross-volume to mountpoint (DI probe), mountpoint-parent preference, pkg_root-only fallback, no-mountpoint fallback, missing-pkg_root fallback, and the Host primitive itself. Each cites the upstream index.ts lines it mirrors.
  • pacquet/crates/config/src/api.rs — new LinkProbe capability trait + Host impl.
  • pacquet/crates/config/src/lib.rs — added store_dir_explicit tracking through the cascade (global config.yaml → pnpm-workspace.yaml → PNPM_CONFIG_* env vars), and a late-stage resolve_store_dir::<Sys> call when the user didn't pin storeDir. Config::current's Sys bound gained + LinkProbe; an inert_link_probe! macro in the test module wires the bound onto every existing test fake.
  • pacquet/crates/config/src/defaults.rs — doc-comment update on default_store_dir pointing at the new re-resolution step.

Test plan

  • cargo nextest run --workspace — 1947/1947 pass, 3 skipped.
  • cargo nextest run -p pacquet-config -E 'test(store_path)' — 11/11 pass.
  • cargo clippy --locked --workspace --all-targets -- --deny warnings clean.
  • just dylint clean.
  • cargo fmt --all -- --check clean.
  • End-to-end: pacquet install --frozen-lockfile in /Volumes/src/tmp/pacquet-store-test (workspace on case-sensitive volume) now links node_modules/is-positive/Volumes/src/.pnpm-store/v11/links/..., matching what pnpm install picks at the same directory.

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

Summary by CodeRabbit

  • New Features

    • Smarter store-directory resolution that picks an appropriate store location across volumes/mounts and avoids cross-volume linking issues when possible.
    • Configuration now detects whether the store directory was explicitly set and only re-resolves the default when appropriate.
  • Documentation

    • Clarified the non-Windows default store-dir behavior: initial default may be re-resolved later unless explicitly pinned.
  • Tests

    • Added comprehensive tests covering store resolution and linkability scenarios.

Review Change Stack

Port pnpm's `getStorePath` / `storePathRelativeToHome` cross-volume
detection to pacquet's `default_store_dir`. When no `storeDir` is
explicitly set (global config.yaml, pnpm-workspace.yaml, or
`PNPM_CONFIG_STORE_DIR`), `Config::current` now probes whether the
project root can be hardlinked into the user's pnpm home dir. If not,
it walks from the filesystem root toward the project to find the
volume mount point and falls back to `<mountpoint>/.pnpm-store` —
matching pnpm's behaviour at
https://github.com/pnpm/pnpm/blob/29a42efc3b/store/path/src/index.ts#L14-L78.

Before this fix, a workspace on a separate volume (e.g. `/Volumes/src/`
on macOS) installed into the home-volume store (`~/Library/pnpm/store`).
When the home volume is case-insensitive and the workspace volume is
case-sensitive, typescript-eslint's path-cache canonicalises against
the home store and then can't locate the same files in TypeScript's
case-sensitive program loaded from the workspace, so `eslint --fix`
fails with "TSConfig does not include this file" on every project file.

The hardlink probe goes through a new `LinkProbe` capability in
`pacquet-config::api`, threaded into `Config::current`'s `Sys` bound
so tests can pin the linkability answer without touching disk.
The production `Host` impl performs real link attempts via
`store_path::host_can_link_between_dirs`. Test fakes get an inert
`LinkProbe` impl via the `inert_link_probe!` macro added to the
`tests` module — every probe returns `false`, the algorithm walks
without finding a mount, and the SmartDefault home store survives
unchanged, so existing cascade assertions stay valid.
@coderabbitai

coderabbitai Bot commented May 21, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 006c6101-87cc-4dbf-b73b-0dab774da4a8

📥 Commits

Reviewing files that changed from the base of the PR and between 2dbd63a and bafff73.

📒 Files selected for processing (1)
  • pacquet/crates/config/src/store_path/tests.rs
📜 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)
  • GitHub Check: Lint and Test (macos-latest)
  • GitHub Check: Lint and Test (windows-latest)
  • GitHub Check: Lint and Test (ubuntu-latest)
  • GitHub Check: Code Coverage
  • GitHub Check: Run benchmark on ubuntu-latest
  • GitHub Check: Run benchmark on ubuntu-latest
  • GitHub Check: Compile & Lint
🧰 Additional context used
📓 Path-based instructions (1)
pacquet/**/*.rs

📄 CodeRabbit inference engine (pacquet/AGENTS.md)

pacquet/**/*.rs: 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/config/src/store_path/tests.rs
🧠 Learnings (2)
📚 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/config/src/store_path/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/config/src/store_path/tests.rs
🔇 Additional comments (1)
pacquet/crates/config/src/store_path/tests.rs (1)

114-114: LGTM!


📝 Walkthrough

Walkthrough

This PR adds hardlink-based store directory resolution to the config crate: a new LinkProbe trait for probing hardlink feasibility, a pnpm-compatible resolve_store_dir algorithm that walks filesystem ancestors to choose a store location, integration into Config::current (conditional re-resolution), and comprehensive tests and test doubles.

Changes

Hardlink-based store directory resolution

Layer / File(s) Summary
LinkProbe capability trait
pacquet/crates/config/src/api.rs
New LinkProbe trait checks hardlink feasibility; Host implements it by delegating to the store_path module. Imports reformatted to multi-line style.
Store path resolution algorithm
pacquet/crates/config/src/store_path.rs
resolve_store_dir computes pnpm-compatible store locations via hardlink-based mountpoint detection with helpers (root_link_target, filesystem_root, next_path) and a production host_can_link_between_dirs probe.
Config integration and re-resolution
pacquet/crates/config/src/lib.rs, pacquet/crates/config/src/defaults.rs
Exports LinkProbe, requires Sys: LinkProbe for Config::current, tracks store_dir_explicit from overlays, and conditionally re-resolves store_dir via resolve_store_dir when not explicitly set. Defaults docs updated to clarify re-resolution behavior.
Test infrastructure
pacquet/crates/config/src/lib.rs
Adds inert_link_probe! macro and applies it to test Sys fakes to satisfy the new LinkProbe bound.
Store path test cases
pacquet/crates/config/src/store_path/tests.rs
Unit tests covering path helpers, same-volume resolution, cross-volume/mount scenarios using a PrefixProbe test double, mount-parent preference, multiple fallback cases, and hardlink-probe success/failure.

Sequence Diagram(s)

sequenceDiagram
  participant Config as Config::current
  participant LinkProbe as Sys: LinkProbe
  participant ResolveStore as store_path::resolve_store_dir
  participant RootWalk as root_link_target

  Config->>LinkProbe: can_link_between_dirs(project, home)?
  alt project and home linkable
    Config->>Config: return home_default
  else cross-volume or same-mount
    Config->>ResolveStore: resolve_store_dir(home, pnpm_home, project)
    ResolveStore->>RootWalk: find first linkable ancestor
    RootWalk->>LinkProbe: can_link_between_dirs(ancestor, project)?
    RootWalk-->>ResolveStore: linkable_ancestor or None
    ResolveStore->>ResolveStore: compute mountpoint/.pnpm-store
    ResolveStore-->>Config: resolved store path or home_default
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • pnpm/pnpm#11736: Modifies Config::current API/signature and Sys bounds; related to trait-bound and config-cascade changes.
  • pnpm/pnpm#11708: Earlier DI seam changes around Config::current and Host capability traits that this PR extends with LinkProbe.
  • pnpm/pnpm#11730: Prior capability-trait refactor in pacquet/crates/config touching the same API surface extended here.

Poem

🐰 I hop through roots and mountpoint light,

I sniff where links may hold by night,
If hardlinks pass, the store will stay,
Else up we climb to find a way,
A cozy pnpm home at play!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title directly describes the main change: implementing cross-volume detection to pick the store on the project's volume, which is the core objective.
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.

✏️ 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 pacquet-store-dir-cross-volume

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.

@github-actions

github-actions Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor

Micro-Benchmark Results

Linux

group                          main                                   pr
-----                          ----                                   --
tarball/download_dependency    1.00      7.6±0.12ms   571.4 KB/sec    1.03      7.8±0.32ms   557.2 KB/sec

@codecov-commenter

codecov-commenter commented May 21, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.03960% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.47%. Comparing base (69f8ea8) to head (bafff73).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
pacquet/crates/config/src/store_path.rs 94.80% 4 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #11804      +/-   ##
==========================================
+ Coverage   87.39%   87.47%   +0.07%     
==========================================
  Files         200      202       +2     
  Lines       23514    23698     +184     
==========================================
+ Hits        20550    20729     +179     
- Misses       2964     2969       +5     

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

- Drop the redundant explicit `[LinkProbe][crate::api::LinkProbe]`
  link target and fully qualify `[StoreDir]` as
  `[pacquet_store_dir::StoreDir]` so the `Doc` job passes under
  `-D rustdoc::broken-intra-doc-links` and `-D rustdoc::redundant-explicit-links`.
- Gate the `PrefixProbe` fake, its `ALLOW_PREFIXES` static, and the
  related imports with `#[cfg(unix)]`. Every consumer is already
  `#[cfg(unix)]`, so on Windows the items were unreferenced and the
  `Lint and Test (windows-latest)` job's clippy run failed under
  `-D dead-code`.
@zkochan zkochan marked this pull request as ready for review May 21, 2026 08:09
@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 →

@coderabbitai coderabbitai Bot added area: global virtual store area: config dependencies Changes related to configDependencies. labels May 21, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

Inline comments:
In `@pacquet/crates/config/src/store_path/tests.rs`:
- Around line 97-117: The tests use global mutable ALLOW_PREFIXES via
PrefixProbe::set_allow which causes cross-talk when run in parallel; wrap each
test scenario that calls PrefixProbe::set_allow and resolve_store_dir in a
serialized helper (e.g., a with_allow(prefixes, || { ... }) that sets
ALLOW_PREFIXES, runs the closure, then clears/restores it) and replace direct
calls to PrefixProbe::set_allow in the PrefixProbe-linked tests (the cases
around resolve_store_dir and the instances referenced by set_allow /
can_link_between_dirs) with that with_allow helper so each scenario is isolated
and ALLOW_PREFIXES is reset after the closure finishes.
🪄 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: 67d95ca9-6a19-49a4-8e04-b153c1656425

📥 Commits

Reviewing files that changed from the base of the PR and between 69f8ea8 and 52625cf.

📒 Files selected for processing (5)
  • pacquet/crates/config/src/api.rs
  • pacquet/crates/config/src/defaults.rs
  • pacquet/crates/config/src/lib.rs
  • pacquet/crates/config/src/store_path.rs
  • pacquet/crates/config/src/store_path/tests.rs
📜 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: Run benchmark on ubuntu-latest
🧰 Additional context used
📓 Path-based instructions (1)
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/config/src/defaults.rs
  • pacquet/crates/config/src/api.rs
  • pacquet/crates/config/src/store_path.rs
  • pacquet/crates/config/src/store_path/tests.rs
  • pacquet/crates/config/src/lib.rs
🧠 Learnings (2)
📚 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/config/src/defaults.rs
  • pacquet/crates/config/src/api.rs
  • pacquet/crates/config/src/store_path.rs
  • pacquet/crates/config/src/store_path/tests.rs
  • pacquet/crates/config/src/lib.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/config/src/defaults.rs
  • pacquet/crates/config/src/api.rs
  • pacquet/crates/config/src/store_path.rs
  • pacquet/crates/config/src/store_path/tests.rs
  • pacquet/crates/config/src/lib.rs
🔇 Additional comments (13)
pacquet/crates/config/src/api.rs (1)

18-22: LGTM!

Also applies to: 82-108, 145-150

pacquet/crates/config/src/store_path.rs (4)

1-50: LGTM!


51-101: LGTM!


103-160: LGTM!


162-201: LGTM!

pacquet/crates/config/src/lib.rs (7)

7-11: LGTM!


970-970: LGTM!


1039-1059: LGTM!


1152-1152: LGTM!

Also applies to: 1176-1176


1188-1212: LGTM!


1249-1277: LGTM!


1334-1334: LGTM!

Also applies to: 1522-1522, 1581-1581, 1846-1846, 1890-1890, 1950-1950, 2002-2002, 2064-2064, 2120-2120, 2161-2161, 2193-2193, 2230-2230, 2268-2268, 2332-2332

pacquet/crates/config/src/defaults.rs (1)

80-92: LGTM!

Comment thread pacquet/crates/config/src/store_path/tests.rs
@github-actions

github-actions Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor

Integrated-Benchmark Report (Linux)

Scenario: Frozen Lockfile

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 2.467 ± 0.078 2.399 2.674 1.01 ± 0.04
pacquet@main 2.442 ± 0.042 2.358 2.518 1.00
pnpm 4.975 ± 0.090 4.867 5.178 2.04 ± 0.05
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 2.4674120568999998,
      "stddev": 0.07802922154642,
      "median": 2.4470448147,
      "user": 2.7645650399999995,
      "system": 3.80627522,
      "min": 2.3993040082,
      "max": 2.6741052202,
      "times": [
        2.6741052202,
        2.4490012172,
        2.4821193672,
        2.4945697052,
        2.4450884122,
        2.3993040082,
        2.4209182692,
        2.4261437332,
        2.4267589942,
        2.4561116422000002
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 2.4423776136,
      "stddev": 0.04210503361848168,
      "median": 2.4478164787,
      "user": 2.7573174399999996,
      "system": 3.7878310199999996,
      "min": 2.3581336582,
      "max": 2.5177116362,
      "times": [
        2.4328508362,
        2.4535490142,
        2.4337042022,
        2.4591469712,
        2.4015131562,
        2.3581336582,
        2.4438100702,
        2.4715337042,
        2.5177116362,
        2.4518228872
      ]
    },
    {
      "command": "pnpm",
      "mean": 4.974697775699999,
      "stddev": 0.0895644154153369,
      "median": 4.975167923700001,
      "user": 8.41047334,
      "system": 4.29014522,
      "min": 4.8674246442,
      "max": 5.1781084032,
      "times": [
        5.0293435252,
        4.9901424092,
        4.9827608562000005,
        4.9138050052,
        4.9675749912,
        5.1781084032,
        4.8674246442,
        4.9341782822,
        4.8767793262,
        5.0068603142
      ]
    }
  ]
}

Scenario: Frozen Lockfile (Hot Cache)

Command Mean [ms] Min [ms] Max [ms] Relative
pacquet@HEAD 702.6 ± 37.5 670.5 769.2 1.00
pacquet@main 706.2 ± 29.4 669.7 764.1 1.01 ± 0.07
pnpm 2626.8 ± 115.1 2452.8 2878.6 3.74 ± 0.26
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 0.7025594730800001,
      "stddev": 0.037475220103137155,
      "median": 0.6813734918800001,
      "user": 0.39165972,
      "system": 1.5658668399999995,
      "min": 0.67048800538,
      "max": 0.76923668438,
      "times": [
        0.76923668438,
        0.72885360438,
        0.68017904338,
        0.67048800538,
        0.70968956738,
        0.6720899153800001,
        0.75940616238,
        0.6786115783800001,
        0.68256794038,
        0.6744722293800001
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 0.70620882138,
      "stddev": 0.02944395883310541,
      "median": 0.69707264788,
      "user": 0.39842811999999994,
      "system": 1.5700981399999996,
      "min": 0.66971366538,
      "max": 0.76412812638,
      "times": [
        0.7073308823800001,
        0.66971366538,
        0.68281064138,
        0.76412812638,
        0.68572205738,
        0.69039617538,
        0.70374912038,
        0.68977326738,
        0.73926735338,
        0.7291969243800001
      ]
    },
    {
      "command": "pnpm",
      "mean": 2.6268179064800004,
      "stddev": 0.1151383420255221,
      "median": 2.61492279188,
      "user": 3.3301411199999995,
      "system": 2.26899734,
      "min": 2.4527928343800003,
      "max": 2.87860446138,
      "times": [
        2.7280399283800003,
        2.60947170938,
        2.60638721638,
        2.87860446138,
        2.62799233438,
        2.55138288438,
        2.4527928343800003,
        2.53697433138,
        2.6561594903800003,
        2.6203738743800002
      ]
    }
  ]
}

… lock

`ALLOW_PREFIXES` was only locked for the read/write of its `Vec`, not
across "set allowlist then probe" — under nextest's default parallel
execution two scenarios could race: scenario A would set its prefixes,
scenario B would overwrite them, and A's `resolve_store_dir` would
observe B's allowlist. Add `PREFIX_PROBE_SCENARIO_LOCK` and a
`PrefixProbe::with_allow(prefixes, body)` helper that holds it across
the entire set-and-probe so scenarios serialise cleanly.

Per CodeRabbit review on #11804.
…tionist

Dylint's `perfectionist::single-letter-generic` flagged the `R`
return-type parameter on `PrefixProbe::with_allow`. Rename to
`Output`.
@zkochan zkochan removed area: config dependencies Changes related to configDependencies. area: global virtual store labels May 21, 2026
@zkochan zkochan merged commit 3a63928 into main May 21, 2026
28 checks passed
@zkochan zkochan deleted the pacquet-store-dir-cross-volume branch May 21, 2026 09:08
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