fix(executor): run lifecycle scripts from long global virtual store slots on Windows - #15147
Ayush442842q wants to merge 18 commits into
Conversation
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? |
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository: pnpm/pnpm/.coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (1)
Included review availability: Your plan provides up to 10 included reviews per hour; 8 remain after this review. 📝 WalkthroughWalkthroughThe change adds shared slash-separated path helpers, uses them for scoped package paths in GVS and executor code, enables GVS lifecycle tests on Windows, and adds a patch changeset. ChangesGlobal virtual store lifecycle scripts
Priority: ➖ Normal Estimated code review effort: 3 (Moderate) | ~25 minutes Change: Bug fix · Severity of issue fixed: Medium Assessment against linked issues
Suggested labels: ✨ Finishing Touches 💡 1🧪 Generate unit tests (beta)
🛠️ Fix failing CI checks 💡
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 #15147 +/- ##
=======================================
Coverage 91.38% 91.38%
=======================================
Files 1362 1363 +1
Lines 182893 182969 +76
=======================================
+ Hits 167131 167203 +72
- Misses 15762 15766 +4 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Integrated-Benchmark Report (Linux)Commit: 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 thresholds on the minimum latency, which is far less perturbed by shared-runner contention (noise only adds time). Scenario: Isolated linker: fresh restore, cold cache + cold store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 2.3219387332000005,
"stddev": 0.08492086791398075,
"median": 2.2943252024,
"user": 1.31137784,
"system": 1.5462546999999998,
"min": 2.2561456624,
"max": 2.5121137284
},
{
"command": "pacquet@main",
"mean": 2.4135974695,
"stddev": 0.13880031023501363,
"median": 2.3784772474,
"user": 1.2811995399999998,
"system": 1.4732870999999998,
"min": 2.2392873474,
"max": 2.6397291914
},
{
"command": "pnpr@HEAD",
"mean": 1.405793864,
"stddev": 0.11573652875313035,
"median": 1.3763974194,
"user": 1.23288984,
"system": 1.5430451,
"min": 1.3086155204,
"max": 1.6799619114
},
{
"command": "pnpr@main",
"mean": 1.4451156814000001,
"stddev": 0.13287892104204538,
"median": 1.4036565879,
"user": 1.1986563400000003,
"system": 1.5416415999999997,
"min": 1.3017060404,
"max": 1.6821186004
}
]
}Scenario: Isolated linker: fresh restore, hot cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 0.20912745776000002,
"stddev": 0.01007548289180315,
"median": 0.20781688476000004,
"user": 0.2378024,
"system": 0.51110268,
"min": 0.19273416626000003,
"max": 0.22542754026000003
},
{
"command": "pacquet@main",
"mean": 0.24930626246000004,
"stddev": 0.05035951427078881,
"median": 0.22807414976,
"user": 0.2610717,
"system": 0.6025672799999999,
"min": 0.19328491026000003,
"max": 0.31320519626000004
},
{
"command": "pnpr@HEAD",
"mean": 0.21474436136000002,
"stddev": 0.010044261452065944,
"median": 0.21690889876000002,
"user": 0.2116786,
"system": 0.50613778,
"min": 0.20078544526000003,
"max": 0.22977535426000004
},
{
"command": "pnpr@main",
"mean": 0.23139130516,
"stddev": 0.01973944072665529,
"median": 0.22360860126000004,
"user": 0.24645720000000004,
"system": 0.53947468,
"min": 0.22151082426000002,
"max": 0.28615411826000003
}
]
}Scenario: Isolated linker: repeat install, hot cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 0.01110597286767123,
"stddev": 0.0027643467473086867,
"median": 0.01081197358,
"user": 0.006776100273972604,
"system": 0.005089645296803655,
"min": 0.00710942858,
"max": 0.01773239258
},
{
"command": "pacquet@main",
"mean": 0.008802641896831682,
"stddev": 0.002733719275776084,
"median": 0.007586212080000001,
"user": 0.005308556633663364,
"system": 0.004115963762376235,
"min": 0.00653110858,
"max": 0.01695950258
},
{
"command": "pnpr@HEAD",
"mean": 0.008299663487185633,
"stddev": 0.0006964647378836183,
"median": 0.00814389708,
"user": 0.004808610059880241,
"system": 0.004141361676646705,
"min": 0.0070206595800000006,
"max": 0.01090004158
},
{
"command": "pnpr@main",
"mean": 0.007852818583095979,
"stddev": 0.0005673868128441512,
"median": 0.00781527758,
"user": 0.004427757151702787,
"system": 0.004042514551083591,
"min": 0.006590418580000001,
"max": 0.01152690158
}
]
}Scenario: Isolated linker: repeat install, cold cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 0.007264892248391607,
"stddev": 0.00045524928233674135,
"median": 0.00723802764,
"user": 0.0040403883916083905,
"system": 0.0036872998601398597,
"min": 0.00625148214,
"max": 0.008747487140000001
},
{
"command": "pacquet@main",
"mean": 0.006966632214766354,
"stddev": 0.0004937983337145535,
"median": 0.0069637981400000005,
"user": 0.004347429532710278,
"system": 0.003076677570093459,
"min": 0.00580029514,
"max": 0.00887571814
},
{
"command": "pnpr@HEAD",
"mean": 0.007190832302650603,
"stddev": 0.00048360664048917903,
"median": 0.007168137640000001,
"user": 0.004058502891566265,
"system": 0.003605713373493977,
"min": 0.00620606614,
"max": 0.00974869714
},
{
"command": "pnpr@main",
"mean": 0.0069814202322190134,
"stddev": 0.0004028820328247432,
"median": 0.0069594691400000006,
"user": 0.004294138789625357,
"system": 0.003106775734870315,
"min": 0.00610092914,
"max": 0.00875862814
}
]
}Scenario: Isolated linker: fresh install, cold cache + cold store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 2.48334781736,
"stddev": 0.1152900664753419,
"median": 2.4397476880599998,
"user": 1.6943682199999999,
"system": 1.6347579999999997,
"min": 2.41830000706,
"max": 2.76843342906
},
{
"command": "pacquet@main",
"mean": 2.49849890396,
"stddev": 0.0689259531840303,
"median": 2.4776401025599997,
"user": 1.8011657199999997,
"system": 1.6489116,
"min": 2.42244445306,
"max": 2.65035694006
},
{
"command": "pnpr@HEAD",
"mean": 1.5210571385600002,
"stddev": 0.12874919718008124,
"median": 1.50151966406,
"user": 1.08108732,
"system": 1.2890732000000003,
"min": 1.38964363506,
"max": 1.7231126490600002
},
{
"command": "pnpr@main",
"mean": 1.49036879826,
"stddev": 0.11107998351302861,
"median": 1.4652117805600002,
"user": 1.11655412,
"system": 1.2272353999999999,
"min": 1.3814650910600001,
"max": 1.75951381806
}
]
}Scenario: Isolated linker: fresh install, hot cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 0.3608288499,
"stddev": 0.028137507978301567,
"median": 0.3480492998,
"user": 0.56910202,
"system": 0.7987947000000001,
"min": 0.3262722983,
"max": 0.4162866383
},
{
"command": "pacquet@main",
"mean": 0.3559148458,
"stddev": 0.029288846973360858,
"median": 0.34578162830000003,
"user": 0.53919812,
"system": 0.8089073000000001,
"min": 0.3244195583,
"max": 0.4260144173
},
{
"command": "pnpr@HEAD",
"mean": 0.26750146350000004,
"stddev": 0.013468699536830312,
"median": 0.2674865388,
"user": 0.21047822,
"system": 0.5058583,
"min": 0.2505037103,
"max": 0.2985661183
},
{
"command": "pnpr@main",
"mean": 0.2969718627,
"stddev": 0.03905801272908955,
"median": 0.2895868053,
"user": 0.24091312,
"system": 0.5712501,
"min": 0.2446094723,
"max": 0.3597510333
}
]
}Scenario: Isolated linker: fresh install, cold cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 1.5730497537199994,
"stddev": 0.06925034273605457,
"median": 1.5420333523199998,
"user": 0.8646922,
"system": 0.9710522000000001,
"min": 1.52510224132,
"max": 1.70553497432
},
{
"command": "pacquet@main",
"mean": 1.5574768671199999,
"stddev": 0.0558017190383619,
"median": 1.53518523432,
"user": 0.8292456999999999,
"system": 0.9454223,
"min": 1.52396945632,
"max": 1.70813202932
},
{
"command": "pnpr@HEAD",
"mean": 0.27034319312000005,
"stddev": 0.007500097304318636,
"median": 0.26834484782,
"user": 0.2084689,
"system": 0.5272427,
"min": 0.26361965932000003,
"max": 0.28828399832
},
{
"command": "pnpr@main",
"mean": 0.27128234792000006,
"stddev": 0.006769226453506438,
"median": 0.27116527481999997,
"user": 0.229005,
"system": 0.5058005,
"min": 0.26184290132000004,
"max": 0.28360913832
}
]
}Scenario: Isolated linker: fresh resolve, hot cache, offline
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 0.19495686479230773,
"stddev": 0.01208623192400324,
"median": 0.1906657391,
"user": 0.20204680923076923,
"system": 0.0704655923076923,
"min": 0.18129951610000003,
"max": 0.22673459610000002
},
{
"command": "pacquet@main",
"mean": 0.1967323866,
"stddev": 0.013967027311822248,
"median": 0.1948142216,
"user": 0.19884418285714286,
"system": 0.0734111857142857,
"min": 0.1794425611,
"max": 0.23001950610000002
},
{
"command": "pnpr@HEAD",
"mean": 0.09023274134242422,
"stddev": 0.0028120837891492272,
"median": 0.09023748010000002,
"user": 0.03373549454545454,
"system": 0.013218572727272731,
"min": 0.08609099710000001,
"max": 0.09575614610000001
},
{
"command": "pnpr@main",
"mean": 0.08887259456875002,
"stddev": 0.002146724509003531,
"median": 0.0884474251,
"user": 0.03150729,
"system": 0.013253403125,
"min": 0.08575047310000002,
"max": 0.09492807610000001
}
]
}Scenario: Isolated linker: peer-heavy resolve, hot cache, offline
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 0.636452921408889,
"stddev": 0.05931568312357954,
"median": 0.6117806825200001,
"user": 0.6692096511111111,
"system": 0.14597464,
"min": 0.5651720115200001,
"max": 0.7175928395200001
},
{
"command": "pacquet@main",
"mean": 0.6047473920755556,
"stddev": 0.044192464103604404,
"median": 0.6107808035200001,
"user": 0.6290084288888887,
"system": 0.14099186222222224,
"min": 0.5533779925200001,
"max": 0.6763702735200001
},
{
"command": "pnpm@HEAD",
"mean": 2.903734755408889,
"stddev": 0.09207016522725738,
"median": 2.9226788805200004,
"user": 4.585938762222222,
"system": 0.2184438622222222,
"min": 2.78388671952,
"max": 3.05143950252
}
]
}Scenario: Isolated linker: linked-workspace resolve, hot cache, offline
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 0.15858164354222223,
"stddev": 0.0020128493349129703,
"median": 0.15810925582000002,
"user": 0.11760723111111109,
"system": 0.07321674222222221,
"min": 0.15668169632,
"max": 0.16554984832
},
{
"command": "pacquet@main",
"mean": 0.16074132926444448,
"stddev": 0.0036163238758597804,
"median": 0.15975233332,
"user": 0.11558861999999998,
"system": 0.08186229777777776,
"min": 0.15628042332,
"max": 0.16788189332
}
]
}Scenario: Isolated linker: fresh restore, cold cache + cold store + cold pnpr
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 4.429545742019999,
"stddev": 0.11958130615549659,
"median": 4.38717327332,
"user": 1.5321806999999998,
"system": 1.9729548399999999,
"min": 4.32806994282,
"max": 4.65696982282
},
{
"command": "pacquet@main",
"mean": 4.43463527332,
"stddev": 0.12367424754740686,
"median": 4.370414201819999,
"user": 1.5404839000000001,
"system": 1.99417884,
"min": 4.33325006582,
"max": 4.72460105182
},
{
"command": "pnpr@HEAD",
"mean": 3.53279855342,
"stddev": 0.13710294503617218,
"median": 3.4662356793200004,
"user": 1.4392002000000002,
"system": 1.8414586400000001,
"min": 3.4033191988200002,
"max": 3.73313429882
},
{
"command": "pnpr@main",
"mean": 3.4794367573200007,
"stddev": 0.09936141279405686,
"median": 3.41998431632,
"user": 1.4334073,
"system": 1.8601518400000003,
"min": 3.39282430782,
"max": 3.66591809682
}
]
}Scenario: GVS linker: fresh restore, hot cache + hot storeSame 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.
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 0.11098435537391306,
"stddev": 0.003030635440105974,
"median": 0.1103800232,
"user": 0.13904435304347823,
"system": 0.11622053478260867,
"min": 0.1069637882,
"max": 0.1182829822
},
{
"command": "pacquet@main",
"mean": 0.11543729749166666,
"stddev": 0.0052204562723426814,
"median": 0.1167610182,
"user": 0.15647194,
"system": 0.11461701666666667,
"min": 0.1048493692,
"max": 0.1223294982
},
{
"command": "pnpr@HEAD",
"mean": 0.1293357304105263,
"stddev": 0.00399233663098175,
"median": 0.1295059122,
"user": 0.15895943999999998,
"system": 0.13228336315789474,
"min": 0.12255108420000001,
"max": 0.1369289792
},
{
"command": "pnpr@main",
"mean": 0.12942880535000004,
"stddev": 0.0045512081887046025,
"median": 0.12978929820000001,
"user": 0.15991408999999998,
"system": 0.13077299999999997,
"min": 0.1230599502,
"max": 0.1436773952
}
]
} |
|
| Project | pnpm's project |
| Branch | pr/15147 |
| Testbed | pnpr |
⚠️ WARNING: No Threshold found!Without a Threshold, no Alerts will ever be generated.
Click here to create a new Threshold
For more information, see the Threshold documentation.
To only post results if a Threshold exists, set the--ci-only-thresholdsflag.
Click to view all benchmark results
| Benchmark | Latency | milliseconds (ms) |
|---|---|---|
| gvs-linker.fresh-restore.hot-cache.hot-store | 📈 view plot | 122.55 ms |
| isolated-linker.fresh-install.cold-cache.cold-store | 📈 view plot | 1,389.64 ms |
| isolated-linker.fresh-install.cold-cache.hot-store | 📈 view plot | 263.62 ms |
| isolated-linker.fresh-install.hot-cache.hot-store | 📈 view plot | 250.50 ms |
| isolated-linker.fresh-resolve.hot-cache.offline | 📈 view plot | 86.09 ms |
| isolated-linker.fresh-restore.cold-cache.cold-store | 📈 view plot | 1,308.62 ms |
| isolated-linker.fresh-restore.cold-cache.cold-store.cold-pnpr | 📈 view plot | 3,403.32 ms |
| isolated-linker.fresh-restore.hot-cache.hot-store | 📈 view plot | 200.79 ms |
| isolated-linker.repeat-install.cold-cache.hot-store | 📈 view plot | 6.21 ms |
| isolated-linker.repeat-install.hot-cache.hot-store | 📈 view plot | 7.02 ms |
Dismissed because a newer commit was pushed; Greptile will re-review the current head.
Dismissed because a newer commit was pushed; Greptile will re-review the current head.
…es dir under GVS When joining scoped package aliases (such as `@scope/name`) onto node_modules, Path::join on Windows appends the string as-is, leaving forward slashes inside the package name component. When spawning lifecycle scripts under the global virtual store, this resulted in an invalid working directory path with mixed separators, causing CreateProcess to fail with os error 267 (ERROR_DIRECTORY). Split dependency aliases and package names on '/' when building node_modules paths in safe_join_modules_dir and virtual_store_dir_for_key to ensure native path separators are used on Windows. Fixes pnpm#15111.
Normalize head and tail path components in ancestor_node_modules_bins so that PATH components on Windows do not retain forward slashes.
Use key.name.to_string() in virtual_store_dir_for_key to avoid manual string slicing and keep it aligned with safe_join_modules_dir.
…le script spawn Ensure working directory paths pass through dunce::simplified when spawning lifecycle scripts and shell commands. On Windows, dunce::canonicalize prepends UNC prefixes (\?\), which causes cmd.exe to fail with OS error 267 (ERROR_DIRECTORY) when set as the working directory. Using dunce::simplified strips UNC prefixes while maintaining native path separators. Fixes pnpm#15111.
4c0a747 to
11f5faa
Compare
Code Review by Qodo
1.
|
PR Code Suggestions ✨Warning
No code suggestions found for the PR. |
push_slash_separated_path is now the common home of the Windows separator argument, and join_global_virtual_store_path points at it instead of restating it. virtual_store_dir_for_key names the field it reads, and the builds module drops its note about a per-test Windows ignore it no longer carries. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A scoped package pushed as one component leaves its `/` inside the Windows path string, which is invisible on Unix. The assertion is written against the foreign separator so it runs on every platform and only Windows can fail it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Restores the blank line before the dependency table too. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
PR Code Suggestions ✨Warning
No code suggestions found for the PR. |
The package map's slot paths and the env installer's config-dependency paths still passed a whole `@scope/name` to Path::join, so on Windows the `/` stayed inside one path component. Route them through pnpm_fs::join_slash_separated_path like every other producer of a global-virtual-store package directory. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Code review by qodo was updated up to the latest commit 3153db6 |
PR Code Suggestions ✨Warning
No code suggestions found for the PR. |
Under enableGlobalVirtualStore a dependency's lifecycle script failed to spawn on Windows with `The directory name is invalid. (os error 267)`. Windows keeps a process's working directory in a MAX_PATH field and refuses a longer one with ERROR_DIRECTORY, the same answer it gives for a directory that is not there. A global virtual store slot reaches that length readily: its path spells out the scope, the name, the version and a 64-character graph digest under whatever store directory the user configured. The bound it applies sits below MAX_PATH and is undocumented, and the error does not say which of the two conditions it meant, so neither is predicted. The spawn goes to the directory the install computed, and shorter spellings of it are tried only once the refusal has come back: first the lexically normalized path, which drops the `..` a relative storeDir leaves behind, then its 8.3 short form, which a volume that still generates short names answers with. Waiting for the refusal is what makes the first of those safe as well as cheap, since `..` through a symlinked directory does not lead where resolving it lexically says it does. When no spelling works the first refusal is reported, naming the directory the install computed. The global-virtual-store build tests no longer skip on Windows. Only one of them installs a package whose slot path stays under the limit, so before this the other six could not run there. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
3153db6 to
fbd1604
Compare
|
Code review by qodo was updated up to the latest commit fbd1604 |
PR Code Suggestions ✨Warning
No code suggestions found for the PR. |
|
Code review by qodo was updated up to the latest commit ff52c3d |
PR Code Suggestions ✨Warning
No code suggestions found for the PR. |
GetShortPathNameW has to be asked in the verbatim `\\?\` form, which is what lets it reach a path over MAX_PATH, and a volume with 8.3 name generation turned off then answers with the very path it was given. Over MAX_PATH `dunce::simplified` cannot shed that prefix either, so pnpm offered as its shorter spelling a path four characters longer than the one Windows had just refused, in a form fewer programs accept. Only a spelling that comes back strictly shorter, and not still verbatim, is worth the second attempt. The Windows test asserts that contract instead of asserting that some spelling exists: whether one does is the volume's business, and the global-virtual-store build tests already cover the fallback end to end. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ff52c3d to
8968c3b
Compare
|
Code review by qodo was updated up to the latest commit 8968c3b |
PR Code Suggestions ✨Warning
No code suggestions found for the PR. |
…slot Lexical normalization resolves `..` the way the path text reads, and a `..` that steps through a symlinked directory does not lead there. A spelling that named a different directory would run the build script in it, which is worse than not running it, so each one is now checked against the slot with `canonicalize` before it is offered. The Windows test shapes its slot like the one the issue reports, `..` and all, so a shorter spelling exists on any volume rather than only on one that still generates 8.3 names, and asserts that a child starts in it. It used to assert only that the spellings were shorter, which held vacuously where no spelling was offered. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Code review by qodo was updated up to the latest commit 0cc1fbe |
PR Code Suggestions ✨Warning
No code suggestions found for the PR. |
This comment has been minimized.
This comment has been minimized.
…sent The rustdoc named a spelling the code no longer has and described what it used to do. The hazard is still worth the warning, so it now says what a lookup by the peer-stripped key does rather than what one did. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Fixed the outside-diff finding in cf64719.
The UNC finding on Written by an agent (Claude Code, claude-opus-5). |
|
Code review by qodo was updated up to the latest commit cf64719 |
PR Code Suggestions ✨Warning
No code suggestions found for the PR. |
Summary
Under
enableGlobalVirtualStore, a dependency's lifecycle script failed to spawn on Windows withThe directory name is invalid. (os error 267). Fixes #15111.Windows keeps a process's working directory in a
MAX_PATHfield and refuses a longer one withERROR_DIRECTORY— the same answer it gives for a directory that is not there. A global virtual store slot reaches that length readily, because its path spells out the scope, the name, the version and a 64-character graph digest beneath the configured store directory. The directory CI was refused is 272 characters:The bound Windows actually applies sits below
MAX_PATHand is not documented — a normalized 259-character spelling of that same slot is refused too — so pnpm does not predict it. The spawn goes to the directory the install computed, and only once the refusal comes back are shorter spellings of it tried, in order:..a relativestoreDirleaves behind. Waiting for the refusal is what makes this safe as well as cheap: a working directory the OS was willing to open is never second-guessed, because..through a symlinked directory does not lead where resolving it lexically says it does.When no spelling works, the first refusal is the one reported, since it names the directory the install computed.
ERR_PNPM_EXECUTOR_SPAWN_LIFECYCLEnow carries that directory. The operating system reports only that it refused one, and under the global virtual store nobody typed the path — this is what identified the length as the cause.The seven
global_virtual_store::buildstests that reach a lifecycle script no longer skip on Windows. Only one of them installs a package whose slot stays under the limit, so six of them could not have run there before.Separately, the slot's
node_modules/<name>was joined as a single path component, leaving the/of a scoped name inside it. That is not whatCreateProcessWrejected, but it is whatCreateSymbolicLinkWrejects — the reasonpnpm_fs::to_native_separatorsexists.pnpm_fs::join_slash_separated_pathandpnpm_fs::push_slash_separated_pathnow make that join in every producer of a slot or modules path:deps-restorer(safe_join_modules_dir,build_modules/slots,virtual_store_layout,package_map),graph-hasher,env-installer, and the executor'sPATHwalk.Squash Commit Body
Checklist
already linked to it solves it.
are implemented in every affected version. The working-directory limit is
the operating system's, and pnpm 11 formats the same 64-hex slot segment,
so it can reach it too; this PR fixes pnpm v12 only.
pnpm changeset) if this PR changes any publishedpackage. Keep it short and written for pnpm users — it becomes a release note.
Written by an agent (Claude Code, claude-opus-5).