fix(pacquet): shorten long virtual store dirnames to avoid ENAMETOOLONG#11768
Conversation
📝 WalkthroughWalkthroughThis PR adds configurable length limits for virtual store directory names. A new ChangesVirtual Store Name Length Limiting
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Possibly related PRs
Suggested labels
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 |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #11768 +/- ##
==========================================
+ Coverage 89.93% 89.98% +0.05%
==========================================
Files 154 155 +1
Lines 18007 18114 +107
==========================================
+ Hits 16194 16300 +106
- Misses 1813 1814 +1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Micro-Benchmark ResultsLinux |
Peer-heavy snapshot keys (e.g. vitest with a dozen browser / coverage / DOM peers) produced flat-name directories that overflowed macOS's 255- byte filename limit, so `install` aborted with errno 63 before unpacking any tarballs. Port the trailing length / case-shortening branch of upstream's `depPathToFilename` (deps/path/src/index.ts:169) so the name becomes `<prefix>_<32-hex-sha256>` capped at `virtualStoreDirMaxLength` bytes (default 120). Extract `create_short_hash` and `shorten_virtual_store_name` into a new `pacquet-crypto-hash` crate mirroring upstream `@pnpm/crypto.hash`; `pacquet-lockfile`, `pacquet-registry`, and `pacquet-store-dir` all consume it instead of duplicating the sha2 + truncate logic. Reported via pnpm/pacquet issue triage (vitest@4.1.6 peer suffix).
Format `pacquet/crates/crypto-hash/Cargo.toml` per the workspace `.taplo.toml` (aligns the `[package]` keys) and downgrade the `pacquet_modules_yaml::DEFAULT_VIRTUAL_STORE_DIR_MAX_LENGTH` reference in `PkgNameVerPeer::to_virtual_store_name` to plain text, since `pacquet-lockfile` deliberately does not depend on `pacquet-modules-yaml` and `RUSTDOCFLAGS=-D warnings` rejected the unresolved intra-doc link.
Integrated-Benchmark Report (Linux)Scenario: Frozen Lockfile
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 2.4428504901999997,
"stddev": 0.2169481811700777,
"median": 2.3724090458999996,
"user": 2.78989724,
"system": 3.6118519,
"min": 2.2993316974,
"max": 3.0421766604,
"times": [
2.4095643073999997,
2.3434474664,
2.3662291933999997,
2.4767670323999997,
3.0421766604,
2.4298565803999996,
2.3785888984,
2.3204860984,
2.3620569674,
2.2993316974
]
},
{
"command": "pacquet@main",
"mean": 2.3251214987,
"stddev": 0.0966415628682156,
"median": 2.2852429349,
"user": 2.7786572400000003,
"system": 3.5587659,
"min": 2.2571990023999997,
"max": 2.5767866163999997,
"times": [
2.2827723744,
2.3230867764,
2.2770417043999998,
2.3834999164,
2.5767866163999997,
2.2877134954,
2.3318645314,
2.2571990023999997,
2.2713614544,
2.2598891154
]
},
{
"command": "pnpm",
"mean": 4.586033402299999,
"stddev": 0.045771362995379976,
"median": 4.5818556058999995,
"user": 7.753002439999999,
"system": 4.0521396,
"min": 4.514921086399999,
"max": 4.6514738804,
"times": [
4.6514738804,
4.636358298399999,
4.5523171564,
4.6470819624,
4.5823375654,
4.5589689964,
4.5862313324,
4.5492700984,
4.514921086399999,
4.5813736463999994
]
}
]
}Scenario: Frozen Lockfile (Hot Cache)
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 0.7032270471600001,
"stddev": 0.027999362900379514,
"median": 0.6949587676600001,
"user": 0.37231,
"system": 1.5996625999999998,
"min": 0.68068759166,
"max": 0.7780875236600001,
"times": [
0.7780875236600001,
0.6931392986600001,
0.7133309606600001,
0.68068759166,
0.6999790786600001,
0.69194673766,
0.6919741136600001,
0.69677823666,
0.70393333966,
0.6824135906600001
]
},
{
"command": "pacquet@main",
"mean": 0.7254999197600001,
"stddev": 0.03717847471667518,
"median": 0.7182915676600001,
"user": 0.37624099999999994,
"system": 1.6095529999999996,
"min": 0.6858013476600001,
"max": 0.7913330026600001,
"times": [
0.7637004886600001,
0.7913330026600001,
0.6858013476600001,
0.7348937896600001,
0.6970871986600001,
0.7519483596600001,
0.7472032016600001,
0.7016893456600001,
0.68824249266,
0.6930999706600001
]
},
{
"command": "pnpm",
"mean": 2.3840229631600005,
"stddev": 0.05903119502592309,
"median": 2.37115541016,
"user": 2.988654,
"system": 2.1929297999999995,
"min": 2.3072265346600003,
"max": 2.51561509466,
"times": [
2.51561509466,
2.4216352696600003,
2.3703252896600002,
2.4254026586600004,
2.3072265346600003,
2.35822760966,
2.37198553066,
2.36309399766,
2.3241657956600004,
2.38255185066
]
}
]
} |
Add `virtual_store_dir_max_length: u64` to `Config` with default 120 (matching `pacquet_modules_yaml::DEFAULT_VIRTUAL_STORE_DIR_MAX_LENGTH`). Wire it through `WorkspaceSettings.virtualStoreDirMaxLength` and the `PNPM_CONFIG_VIRTUAL_STORE_DIR_MAX_LENGTH` env-overlay so users can override the threshold via `pnpm-workspace.yaml`, global `config.yaml`, or environment variables — mirroring upstream `Config.virtualStoreDirMaxLength`. The three flat-name call sites (`install_without_lockfile.rs`, `install_package_from_registry.rs`, `virtual_store_layout.rs`) and the `.modules.yaml` writer now read the configured value instead of the hardcoded constant. `VirtualStoreLayout::legacy` takes the value as an explicit second arg so test fixtures don't silently inherit a default.
There was a problem hiding this comment.
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/crypto-hash/src/lib.rs`:
- Around line 63-69: The code appends "_<32-hex-hash>" unconditionally which can
exceed max_length when max_length < 33; change the logic in the function using
max_length, filename, and create_short_hash so that if max_length <= 33 you
return a portion of the hex hash that fits (no leading underscore) truncated to
max_length (hex is ASCII so simple slicing is fine), otherwise keep the existing
behavior: compute cap = max_length.saturating_sub(33), pick a char-boundary-safe
filename prefix using boundary on filename, then format!("{}_{}", prefix, hash).
🪄 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: 7a9b814f-b7fb-4b79-a400-4412a8147a12
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (25)
Cargo.tomlpacquet/crates/config/src/defaults.rspacquet/crates/config/src/env_overlay.rspacquet/crates/config/src/lib.rspacquet/crates/config/src/workspace_yaml.rspacquet/crates/crypto-hash/Cargo.tomlpacquet/crates/crypto-hash/src/lib.rspacquet/crates/lockfile/Cargo.tomlpacquet/crates/lockfile/src/pkg_name_ver_peer.rspacquet/crates/lockfile/src/pkg_name_ver_peer/tests.rspacquet/crates/package-manager/Cargo.tomlpacquet/crates/package-manager/src/build_modules/tests.rspacquet/crates/package-manager/src/create_symlink_layout/tests.rspacquet/crates/package-manager/src/create_virtual_dir_by_snapshot/tests.rspacquet/crates/package-manager/src/hoist/tests.rspacquet/crates/package-manager/src/install.rspacquet/crates/package-manager/src/install_package_from_registry.rspacquet/crates/package-manager/src/install_package_from_registry/tests.rspacquet/crates/package-manager/src/install_without_lockfile.rspacquet/crates/package-manager/src/link_bins/tests.rspacquet/crates/package-manager/src/symlink_direct_dependencies/tests.rspacquet/crates/package-manager/src/virtual_store_layout.rspacquet/crates/registry/src/package_version.rspacquet/crates/store-dir/Cargo.tomlpacquet/crates/store-dir/src/project_registry.rs
💤 Files with no reviewable changes (1)
- pacquet/crates/registry/src/package_version.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). (2)
- GitHub Check: ubuntu-latest / Node.js 24 / Test
- 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 firespnpm:<channel>events throughglobalLogger,logger.debug(), orstreamParser.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 plainStringor&str.
If upstream always validates before construction, validate in pacquet's wrapper too. The wrapper must construct only viaTryFrom<String>and/orFromStr. Do not provide an infallible public constructor.
If upstream never validates, just brand for type-safety. Expose an infallibleFrom<String>(andFrom<&str>when convenient).
If upstream occasionally constructs without validation, exposefrom_str_uncheckedas 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 manualimplonly when conversion needs custom logic.
String-literal unions should becomeenums, 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 (&Pathover&PathBuf,&strover&String) when it doesn't force extra copies.
PreferArc::clone(&x)/Rc::clone(&x)overx.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. Writeuse super::{Foo, bar}instead ofuse super::*;. Two forms stay allowed: external-crate preludes likeuse rayon::prelude::*;and root-of-module re-...
Files:
pacquet/crates/config/src/env_overlay.rspacquet/crates/package-manager/src/install_package_from_registry/tests.rspacquet/crates/config/src/defaults.rspacquet/crates/package-manager/src/install_package_from_registry.rspacquet/crates/package-manager/src/create_virtual_dir_by_snapshot/tests.rspacquet/crates/crypto-hash/src/lib.rspacquet/crates/lockfile/src/pkg_name_ver_peer.rspacquet/crates/package-manager/src/install_without_lockfile.rspacquet/crates/package-manager/src/hoist/tests.rspacquet/crates/lockfile/src/pkg_name_ver_peer/tests.rspacquet/crates/config/src/workspace_yaml.rspacquet/crates/package-manager/src/create_symlink_layout/tests.rspacquet/crates/package-manager/src/link_bins/tests.rspacquet/crates/package-manager/src/install.rspacquet/crates/package-manager/src/symlink_direct_dependencies/tests.rspacquet/crates/config/src/lib.rspacquet/crates/package-manager/src/build_modules/tests.rspacquet/crates/package-manager/src/virtual_store_layout.rspacquet/crates/store-dir/src/project_registry.rs
🔇 Additional comments (24)
Cargo.toml (1)
18-18: LGTM!pacquet/crates/crypto-hash/Cargo.toml (1)
1-23: LGTM!pacquet/crates/crypto-hash/src/lib.rs (1)
1-62: LGTM!Also applies to: 72-118
pacquet/crates/config/src/defaults.rs (1)
220-230: LGTM!pacquet/crates/config/src/env_overlay.rs (1)
132-132: LGTM!pacquet/crates/config/src/lib.rs (1)
25-27: LGTM!Also applies to: 295-314, 2161-2220
pacquet/crates/config/src/workspace_yaml.rs (1)
117-117: LGTM!Also applies to: 410-410
pacquet/crates/lockfile/Cargo.toml (1)
14-14: LGTM!pacquet/crates/lockfile/src/pkg_name_ver_peer.rs (1)
2-2: LGTM!Also applies to: 15-43
pacquet/crates/lockfile/src/pkg_name_ver_peer/tests.rs (1)
4-5: LGTM!Also applies to: 43-43, 62-85
pacquet/crates/package-manager/src/virtual_store_layout.rs (1)
75-85: LGTM!Also applies to: 96-101, 175-188, 245-249, 280-285
pacquet/crates/package-manager/Cargo.toml (1)
15-15: LGTM!pacquet/crates/package-manager/src/install_package_from_registry.rs (1)
8-8: LGTM!Also applies to: 109-112
pacquet/crates/package-manager/src/install_package_from_registry/tests.rs (1)
33-33: LGTM!pacquet/crates/package-manager/src/install_without_lockfile.rs (1)
12-12: LGTM!Also applies to: 311-314, 379-386
pacquet/crates/package-manager/src/install.rs (1)
18-19: LGTM!Also applies to: 703-703
pacquet/crates/store-dir/Cargo.toml (1)
14-15: LGTM!pacquet/crates/store-dir/src/project_registry.rs (1)
18-18: LGTM!pacquet/crates/package-manager/src/build_modules/tests.rs (1)
293-296: LGTM!Also applies to: 365-368, 425-428, 508-511, 643-646, 710-713, 770-773, 1008-1011, 1120-1123, 1241-1244, 1473-1476, 1582-1585, 1662-1665
pacquet/crates/package-manager/src/create_symlink_layout/tests.rs (1)
49-52: LGTM!Also applies to: 98-101, 150-153, 189-192, 220-223
pacquet/crates/package-manager/src/create_virtual_dir_by_snapshot/tests.rs (1)
63-66: LGTM!pacquet/crates/package-manager/src/hoist/tests.rs (1)
401-404: LGTM!pacquet/crates/package-manager/src/link_bins/tests.rs (1)
52-55: LGTM!Also applies to: 123-126, 151-154, 192-195, 246-249, 299-302, 327-330, 357-360, 508-511
pacquet/crates/package-manager/src/symlink_direct_dependencies/tests.rs (1)
85-88: LGTM!Also applies to: 211-214, 295-298, 357-360, 439-442, 507-510, 585-588
Summary
installaborted withFailed to recursively create … File name too long (os error 63)before unpacking any tarballs. The user-reported case was a Vitest install with ~10 browser / coverage / DOM peers in the suffix.depPathToFilenameso the dirname becomes<prefix>_<32-hex-sha256>capped atvirtualStoreDirMaxLengthbytes (default 120). Applies to bothPkgNameVerPeer::to_virtual_store_name(lockfile path) andPackageVersion::to_virtual_store_name(no-lockfile path).create_short_hashandshorten_virtual_store_nameinto a newpacquet-crypto-hashcrate mirroring upstream@pnpm/crypto.hash.pacquet-lockfile,pacquet-registry, andpacquet-store-dirconsume it instead of duplicating the sha2 + truncate logic.Out of scope (follow-up)
virtualStoreDirMaxLengthis currently read from theDEFAULT_VIRTUAL_STORE_DIR_MAX_LENGTH = 120constant at the callers. Adding aConfigfield so users can override it via.npmrc/ workspace yaml is a single-site swap once the plumbing exists — left out to keep this PR scoped to the reported bug.Test plan
cargo nextest run --workspace— 1558/1558 passcargo clippy --locked --workspace --all-targets -- --deny warnings— cleancargo check --locked --workspace --all-targets— cleanto_virtual_store_name_shortens_user_reported_vitest_casetest inpacquet-lockfilereproduces the reported snapshot key and asserts the shortened name is exactlymax_lengthbytes with a 32-hex-char trailing hash.shorten_*tests inpacquet-crypto-hashcover identity (below threshold), exact-length shortening (above threshold), and thefile+case-guard escape.Written by an agent (Claude Code, claude-opus-4-7).
Summary by CodeRabbit
virtualStoreDirMaxLengthsetting (default: 120 characters) for virtual store directory namingpnpm-workspace.yamlor thePNPM_CONFIG_VIRTUAL_STORE_DIR_MAX_LENGTHenvironment variable