Skip to content

fix: global installs respect build policy from global config.yaml when GVS is enabled#11363

Merged
zkochan merged 13 commits into
pnpm:mainfrom
HaleTom:fix-global-allow-builds
May 16, 2026
Merged

fix: global installs respect build policy from global config.yaml when GVS is enabled#11363
zkochan merged 13 commits into
pnpm:mainfrom
HaleTom:fix-global-allow-builds

Conversation

@HaleTom

@HaleTom HaleTom commented Apr 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #9249

Global installs respect the global config build policy (e.g., dangerouslyAllowAllBuilds) when the global virtual store is enabled.

Problem

The global virtual-store (GVS) default allowBuilds = {} was applied before workspace manifest settings were read and before global config values (stripped by extractAndRemoveDependencyBuildOptions) were re-applied via globalDepsBuildConfig. This caused hasDependencyBuildOptions to return true (because {} is not null), blocking restoration of global config values like dangerouslyAllowAllBuilds. As a result, global installs skipped all build scripts even when the config explicitly allowed them.

Fix

Moves the GVS default to after workspace manifest reading and globalDepsBuildConfig re-application, with an explicit null/true check instead of hasDependencyBuildOptions():

if (
  pnpmConfig.enableGlobalVirtualStore &&
  pnpmConfig.allowBuilds == null &&
  pnpmConfig.dangerouslyAllowAllBuilds !== true
) {
  pnpmConfig.allowBuilds = {}
}

This ensures:

  1. Workspace manifest allowBuilds takes precedence (if present)
  2. Global config dangerously-allow-all-builds is properly restored (if set and no workspace policy exists)
  3. Empty {} is only applied as a last resort when no policy is configured anywhere

Summary by CodeRabbit

  • Bug Fixes
    • Fixed build policy configuration precedence in global install scenarios. Workspace manifest build settings now correctly override global configuration settings, and the dangerouslyAllowAllBuilds option is properly respected when enabled globally.

Review Change Stack

…dConfig re-application

The GVS default allowBuilds = {} was applied too early — before
workspace manifest settings were read and before .npmrc values
(dangerouslyAllowAllBuilds) were re-applied via globalDepsBuildConfig.

This caused hasDependencyBuildOptions() to return true (because {}} is
not null), blocking restoration of .npmrc values. Global installs
with GVS enabled would silently skip all build scripts even when
the config explicitly allowed them.

This fix moves the GVS default to after both workspace manifest
reading and globalDepsBuildConfig re-application, so that:
1. Workspace manifest allowBuilds takes precedence (if present)
2. .npmrc dangerously-allow-all-builds is properly restored
3. Empty {}} is only applied as a last resort

Closes pnpm#9249
Copilot AI review requested due to automatic review settings April 25, 2026 01:01
@HaleTom HaleTom requested a review from zkochan as a code owner April 25, 2026 01:01
@welcome

welcome Bot commented Apr 25, 2026

Copy link
Copy Markdown

💖 Thanks for opening this pull request! 💖
Please be patient and we will get back to you as soon as we can.

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.

Pull request overview

Fixes a config-merge ordering issue so global installs (pnpm add -g) correctly honor dependency build policy from .npmrc / global workspace manifest when Global Virtual Store (GVS) is enabled.

Changes:

  • Moves the GVS defaulting of allowBuilds = {} to run after workspace-manifest settings are loaded and .npmrc-derived build policy is re-applied.
  • Adds a changeset for a patch release of @pnpm/config.reader and pnpm.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
config/reader/src/index.ts Reorders when allowBuilds is defaulted under GVS to avoid blocking restoration of .npmrc build-policy settings.
.changeset/fix-global-allow-builds.md Adds release notes for the fix (patch).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread config/reader/src/index.ts Outdated
From PR review discussion_r3141002317:

- Replace hasDependencyBuildOptions() == null with hasDependencyBuildOptions()
  so the GVS default only applies when no build policy at all is
  configured (not even dangerouslyAllowAllBuilds). This is cleaner because
  the condition now matches the re-application guard on the line
  immediately before it.

- Add regression test verifying that dangerouslyAllowAllBuilds with GVS
  preserves allowBuilds when no global workspace manifest exists.
@HaleTom HaleTom force-pushed the fix-global-allow-builds branch from c99f6f9 to 475ef6b Compare April 25, 2026 18:47
@HaleTom

HaleTom commented Apr 26, 2026

Copy link
Copy Markdown
Contributor Author

My agent had a brain-fart on the commit comment in c99f6f9, hence why 475ef6b.

@HaleTom

HaleTom commented Apr 26, 2026

Copy link
Copy Markdown
Contributor Author

LOL, my agent reviewed my communication style on this comment

Never use casual slang like "brain-fart" on public project PRs. Use neutral technical language: "An error in an earlier commit message required a follow-up correction."

We had a conversation about owning self-made mistakes :)

@HaleTom

HaleTom commented Apr 27, 2026

Copy link
Copy Markdown
Contributor Author

Update on related issues #9073 and #9478:

After investigation, these are both the same root cause as this PR's fix — but in the v10 config reader, not v11:

This PR does NOT close #9073 or #9478 — it fixes only the v11 GVS ordering bug (PR #11363).

The proper fix for #9073/#9478 is the v10 backport (adding else if (cliOptions['global']) to read workspace manifest from globalPkgDir), which is tracked separately.

@HaleTom HaleTom left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@zkochan Requesting copilot re-review after incorporating feedback (changed condition from allowBuilds == null to !hasDependencyBuildOptions and added regression test).

Comment thread .changeset/fix-global-allow-builds.md Outdated
@zkochan

zkochan commented Apr 27, 2026

Copy link
Copy Markdown
Member

GVS without an allow list kinda doesn't make sense at the moment unfortunately. We use the allow list to detect which packages should include the node.js version in the hash during installation. There's no other way to do it because the registry doesn't give us information about which dependencies should be built during resolution.

So, if you allow all builds, gvs will have all package duplicated for all node.js runtimes.

Copilot AI review requested due to automatic review settings April 27, 2026 23:19

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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread config/reader/test/index.ts Outdated
Comment thread config/reader/src/index.ts Outdated
Comment thread config/reader/src/index.ts Outdated
Comment thread config/reader/test/index.ts Outdated
- Fix unreachable GVS allowBuilds default: hasDependencyBuildOptions()
  always returns true after globalDepsBuildConfig re-applies defaults
  (dangerouslyAllowAllBuilds: false is != null). Replace with explicit
  allowBuilds == null && dangerouslyAllowAllBuilds !== true check.
- Rename .npmrc references to global config.yaml in changeset, comments,
  and test names (zkochan: v11 reads from global rc file, not .npmrc).
- Add try/finally env cleanup for XDG_CONFIG_HOME and PNPM_HOME in tests.
- Add test for workspace manifest allowBuilds precedence over config.yaml.
auto-merge was automatically disabled April 28, 2026 17:54

Head branch was pushed to by a user without write access

- Use import.meta.dirname/global/v11 for globalPkgDir (matches env.PNPM_HOME)
- Fix assertion: dangerouslyAllowAllBuilds coexists with allowBuilds
- Clean up global/v11 directory in finally block to prevent test leakage
Copilot AI review requested due to automatic review settings April 28, 2026 18:20

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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread config/reader/test/index.ts Outdated
Comment thread config/reader/test/index.ts Outdated
…s; clean up parent global/ dir

Fixes two PR pnpm#11363 review threads:
1. allowBuilds in workspace manifest must be Record<string, boolean>,
   not array — createAllowBuildFunction uses Object.entries()
2. Remove empty config/reader/test/global/ directory after test
@HaleTom HaleTom marked this pull request as draft April 29, 2026 03:05
Tom Hale and others added 2 commits April 29, 2026 10:26
- Update changeset: use camelCase dangerouslyAllowAllBuilds (YAML key, not .npmrc)
- Add enableGlobalVirtualStore assertion to first GVS test
- Add comment explaining dangerouslyAllowAllBuilds coexistence on config object
…T_VERSION, try/finally

- Move XDG_CONFIG_HOME mutation and file setup inside try blocks
  so env is always restored even if setup throws
- Replace hard-coded v11 with GLOBAL_LAYOUT_VERSION import
- Fix corrupted try/finally in workspace manifest precedence test
  (missing finally block and mangled expect line from prior bad edit)
- Reword comment: enableGlobalVirtualStore defaults to true for
  global installs, not \"when not in CI\"
Copilot AI review requested due to automatic review settings April 29, 2026 03:43

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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread config/reader/test/index.ts Outdated
Comment thread config/reader/test/index.ts
Comment thread config/reader/test/index.ts
HaleTom

This comment was marked as low quality.

Copilot AI review requested due to automatic review settings April 29, 2026 06:57

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.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 8 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread .github/workflows/opencode.yml Outdated
Comment thread .changeset/fix-global-allow-builds.md Outdated
Comment thread .changeset/fix-global-workspace-dir-v10.md Outdated
Comment thread PR.md Outdated
Comment thread REVIEW-2026-04-29T10:22:18+07:00.md Outdated
Comment thread .opencode/plans/uq2-skill-update-patches.md Outdated
Comment thread config/reader/test/index.ts Outdated
Comment thread config/reader/test/index.ts Outdated
@HaleTom HaleTom marked this pull request as draft April 29, 2026 13:53
@HaleTom

HaleTom commented Apr 29, 2026

Copy link
Copy Markdown
Contributor Author

Production-ready bar for this PR

  • Correctness: allowBuilds = {} default must NOT override workspace manifest or dangerouslyAllowAllBuilds from global config.yaml. The ordering guard allowBuilds == null && dangerouslyAllowAllBuilds !== true must be correct in all code paths (global workspace manifest present, absent, global config present, absent, and combinations).
  • Architecture: The GVS default must run AFTER globalDepsBuildConfig re-application and AFTER workspace manifest reading. The fix preserves the existing phase ordering without introducing new coupling.
  • No over-engineering: The guard is a 3-condition check — proportionate to the problem. No new abstractions are introduced.
  • Comments: The comment on the GVS default block must accurately describe why the condition is ordered this way and what happens when each policy source is present.
  • Tests: Both key scenarios must be tested: (1) workspace manifest allowBuilds + dangerouslyAllowAllBuilds coexist, (2) dangerouslyAllowAllBuilds alone without workspace manifest. Tests must clean up env and filesystem state.
  • Performance: This is config-reading code (once per pnpm install), not a hot path. No performance concern.
  • Operational safety: The change only reorders an existing default allowBuilds = {} assignment. It does not change the semantics of allowBuilds or dangerouslyAllowAllBuilds — the same values reach the same downstream functions. Safe to revert.
  • Changeset accuracy: The changeset must not reference .npmrc (the bug is about global config.yaml / workspace manifest, not .npmrc).
  • No scope creep: No unrelated refactors, no new abstractions, no changes outside the config reader.

Findings

1. Correctness & functional completeness

[NON-BLOCKING] Second test does not assert allowBuilds guard is NOT applied

  • Type: Plausible risk
  • Evidence: config/reader/test/index.ts:1793-1796
  • Why it matters: The second test ("dangerouslyAllowAllBuilds is preserved when no workspace manifest exists") asserts config.allowBuilds is undefined and config.dangerouslyAllowAllBuilds is true. This verifies the guard's dangerouslyAllowAllBuilds !== true branch. However, it does NOT separately verify that when BOTH allowBuilds == null AND dangerouslyAllowAllBuilds !== true, the allowBuilds = {} default IS applied (the third branch of the guard). The first test does not test this branch either — it tests the case where allowBuilds is set by the workspace manifest, so the allowBuilds == null condition is false and the = {} default is not reached. A simple third test (GVS enabled, no workspace manifest, no dangerouslyAllowAllBuilds) would confirm the = {} default still fires when it should.
  • Recommendation: Add a brief third test: global install with GVS enabled, no workspace manifest, no dangerouslyAllowAllBuildsconfig.allowBuilds should be {} (engine-agnostic hashes for pure-JS). This is the normal case that was already working; a regression test confirms the reordering didn't break it.
  • Confidence: Medium

[NIT] dangerouslyAllowAllBuilds !== true does not distinguish false from undefined

  • Type: Plausible risk
  • Evidence: config/reader/src/index.ts:504
  • Why it matters: The guard dangerouslyAllowAllBuilds !== true leaves the allowBuilds = {} default active when dangerouslyAllowAllBuilds is false (explicitly set in config). If dangerouslyAllowAllBuilds: false is set alongside no workspace manifest, the result is allowBuilds: {}, which means "no packages can build" — the same semantic as dangerouslyAllowAllBuilds: false. This is correct behavior (allowBuilds: {} means "only packages listed may build" and the list is empty). But it's worth noting because the subtle distinction between false and undefined could matter if the semantics of allowBuilds: {} ever change.
  • Recommendation: No code change needed. The behavior is correct. Consider adding a brief inline comment if this distinction comes up in review.
  • Confidence: High (the behavior is correct; the concern is about future-proofing)

2. Architecture & boundary integrity

No issues found in this area based on the diff and reviewed context.

The fix moves 5 lines down ~120 lines past globalDepsBuildConfig re-application and workspace manifest reading. This is the correct architectural fix — the GVS default must come after all policy sources have been resolved. No new cross-layer coupling is introduced.

3. Code clarity, clean code & maintainability

[NIT] Guard condition is a 3-clause conjunction that requires careful reading

  • Type: Plausible risk
  • Evidence: config/reader/src/index.ts:502-505
  • Why it matters: enableGlobalVirtualStore && allowBuilds == null && dangerouslyAllowAllBuilds !== true requires the reader to mentally trace 3 policy sources (GVS toggle, workspace manifest, global config) and understand that allowBuilds == null means "no workspace manifest policy" and dangerouslyAllowAllBuilds !== true means "no explicit permissive policy". The comment above helps, but the guard itself is dense.
  • Recommendation: The comment is sufficient documentation. No code change needed — extracting this into a named function would be over-engineering for a 3-clause guard that is used exactly once.
  • Confidence: High

4. Comments & code documentation

No issues found. The comment at config/reader/src/index.ts:495-500 accurately explains the why (engine-agnostic vs engine-specific hashes) and the when (only when no policy is configured). The test comments at lines 1755-1758 and 1792-1795 explain the regression context clearly.

5. Tests & validation

[NON-BLOCKING] First test does not verify that workspace manifest allowBuilds produces correct createAllowBuildFunction behavior

  • Type: Plausible risk
  • Evidence: config/reader/test/index.ts:1750-1751
  • Why it matters: The test asserts config.allowBuilds equals { '@some/pkg': true, esbuild: true } after getConfig(). This verifies the config object, but not the downstream behavior. createAllowBuildFunction iterates Object.entries(opts.allowBuilds) — the test uses the Record<string, boolean> form which is correct. But the test doesn't call createAllowBuildFunction to verify that @some/pkg is allowed and other packages return undefined. This is a config-reader unit test, so testing the config object is appropriate scope; the build policy has its own tests. No change needed, but worth noting.
  • Recommendation: No change needed — appropriate scope for a config-reader test.
  • Confidence: High

[NIT] Test relies on import.meta.dirname for PNPM_HOME without documenting the path contract

  • Type: Verified issue
  • Evidence: config/reader/test/index.ts:36
  • Why it matters: PNPM_HOME: import.meta.dirname means globalPkgDir resolves to <import.meta.dirname>/global/v11/. The first test creates a workspace manifest at that exact path. If the test file is ever moved, the path contract changes silently. This is a pre-existing convention used by all global tests in the file, not introduced by this PR.
  • Recommendation: No change needed — existing convention.
  • Confidence: High

6. Performance

No performance concerns. This is config-reading code executed once per pnpm install. The moved block is a simple conditional assignment.

7. Operational risk

No operational concerns. The change is a reordering of an existing default assignment. The same values flow to the same downstream functions (createAllowBuildFunction, graph-hasher). Safe to revert — the old behavior is restored by moving the block back up.

8. Adversarial review

Attempting to falsify: "What if globalDepsBuildConfig is empty?"

If globalDepsBuildConfig is an empty object (no dangerouslyAllowAllBuilds, no allowBuilds in config), then:

  • extractAndRemoveDependencyBuildOptions strips both keys → globalDepsBuildConfig = {}
  • hasDependencyBuildOptions(pnpmConfig) returns false (both keys are now null)
  • Object.assign(pnpmConfig, globalDepsBuildConfig) → no-op (empty object)
  • Guard: allowBuilds == null && dangerouslyAllowAllBuilds !== truetrue
  • Result: allowBuilds = {} ✅ Engine-agnostic hashes for pure-JS. Correct.

Attempting to falsify: "What if workspace manifest has allowBuilds AND dangerouslyAllowAllBuilds is set?"

This is exactly test 1. The workspace manifest sets allowBuilds via addSettingsFromWorkspaceManifestToConfig. dangerouslyAllowAllBuilds was already re-applied by Object.assign(pnpmConfig, globalDepsBuildConfig). Guard: allowBuilds == nullfalse. Result: allowBuilds keeps workspace manifest value, dangerouslyAllowAllBuilds stays true. ✅ Both coexist. Correct.

Attempting to falsify: "What if dangerouslyAllowAllBuilds: false?"

  • extractAndRemoveDependencyBuildOptions strips dangerouslyAllowAllBuilds
  • globalDepsBuildConfig = { dangerouslyAllowAllBuilds: false }
  • hasDependencyBuildOptions(pnpmConfig) returns false (both keys null after stripping)
  • Object.assign(pnpmConfig, globalDepsBuildConfig)dangerouslyAllowAllBuilds = false
  • Guard: allowBuilds == null && dangerouslyAllowAllBuilds !== truetrue (false !== true)
  • Result: allowBuilds = {} — means "no packages may build". Same behavior as dangerouslyAllowAllBuilds: false. ✅ Correct.

Attempting to falsify: "What about the !hasDependencyBuildOptions check above the guard?"

Line 492: if (!hasDependencyBuildOptions(pnpmConfig)) — this re-applies globalDepsBuildConfig. After this line, pnpmConfig has both dangerouslyAllowAllBuilds and allowBuilds re-set from globalDepsBuildConfig. THEN the GVS guard checks these values. The ordering is: re-apply → guard. ✅ Correct.

What assumption is most likely to be wrong?

The assumption that addSettingsFromWorkspaceManifestToConfig always sets allowBuilds when the workspace manifest contains it. Looking at the code (line 760-799): it iterates Object.entries(newSettings) and sets each key on pnpmConfig. This is a general-purpose "copy all settings" function — not specific to allowBuilds. The risk: if getOptionsFromPnpmSettings transforms or omits allowBuilds. But since the first test confirms config.allowBuilds has the expected value, this is verified. ✅

What test gives false confidence?

The first test uses { '@some/pkg': true, esbuild: true } — valid Record form. If a future contributor copies this test but uses array form ['@some/pkg', 'esbuild'], the test would pass (config object has the array) but createAllowBuildFunction would iterate Object.entries(['@some/pkg', 'esbuild']){ '0': '@some/pkg', '1': 'esbuild' } → package names '0' and '1'. This is a pre-existing API concern, not introduced by this PR.

What I could not fully verify

  • Full test suite execution: The pnpm-v11 monorepo requires node>=22.13 with node_modules installed. Prior sessions ran eslint (0 errors) and the @pnpm/config.reader tests (2 GVS tests passed, 20 global tests passed, 2 pre-existing failures in unrelated tests). CI is the authoritative verification.
  • createAllowBuildFunction integration: The config-reader tests verify the config object. I did not verify that createAllowBuildFunction produces the expected AllowBuild function for each scenario, but this is out of scope for a config-reader unit test.
  • E2E global install with GVS: No end-to-end test was run that actually performs pnpm add -g <pkg> with GVS enabled and verifies build scripts run. The unit tests verify the config layer; E2E verification would require a full pnpm build.

Final verdict

✅ Ready to merge — no blocking issues.

The fix is correct, proportionate, and well-tested for the two key scenarios. The guard ordering is verified by code tracing. The changeset is accurate. Two non-blocking suggestions (add a third test for the allowBuilds = {} default path, consider the false vs undefined edge case) are improvements, not merge gates.

@HaleTom

HaleTom commented May 1, 2026

Copy link
Copy Markdown
Contributor Author

@zkochan are you able to approve / restart the remaining hung(?) CI workflows? Thanks!

@zkochan zkochan marked this pull request as ready for review May 16, 2026 00:06
Copilot AI review requested due to automatic review settings May 16, 2026 00:06
@coderabbitai

coderabbitai Bot commented May 16, 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: a5ebba3d-6ca9-403d-952c-cefb21f9ebac

📥 Commits

Reviewing files that changed from the base of the PR and between 763ddf1 and f0d4489.

📒 Files selected for processing (3)
  • .changeset/fix-global-allow-builds.md
  • config/reader/src/index.ts
  • config/reader/test/index.ts
📜 Recent review details
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Use Standard Style with modifications: trailing commas are used, functions are preferred over classes, functions are declared after they are used (hoisting is relied upon).
Functions should have no more than two or three arguments. If a function needs more parameters, use a single options object instead.
Maintain import order: (1) Standard libraries, (2) External dependencies (sorted alphabetically), (3) Relative imports.

Files:

  • config/reader/test/index.ts
  • config/reader/src/index.ts
🧠 Learnings (1)
📚 Learning: 2026-05-14T09:04:00.133Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11622
File: resolving/npm-resolver/test/publishedBy.test.ts:350-354
Timestamp: 2026-05-14T09:04:00.133Z
Learning: In the pnpm/pnpm repository, ESLint is the authoritative style linter. Do not raise review findings for missing trailing commas in multiline function calls (e.g., `fs.writeFileSync(...)`) when this repo’s ESLint configuration does not report them and lint passes. Prefer deferring to the ESLint results for this specific trailing-comma rule rather than enforcing it manually in code review.

Applied to files:

  • config/reader/test/index.ts
  • config/reader/src/index.ts
🔇 Additional comments (3)
config/reader/src/index.ts (1)

495-507: LGTM!

.changeset/fix-global-allow-builds.md (1)

1-13: LGTM!

config/reader/test/index.ts (1)

7-7: LGTM!

Also applies to: 1716-1811


📝 Walkthrough

Walkthrough

This PR fixes the ordering of GVS allowBuilds default application in pnpm's config reader. The fix moves the allowBuilds = {} default to occur after workspace manifest reading and global config re-application, ensuring workspace policy takes precedence while preserving global dangerouslyAllowAllBuilds settings when no workspace policy exists.

Changes

GVS Build Policy Default Fix

Layer / File(s) Summary
GVS allowBuilds default logic fix
config/reader/src/index.ts, .changeset/fix-global-allow-builds.md
Removes the unconditional GVS default assignment and replaces it with conditional logic that only applies allowBuilds = {} when enableGlobalVirtualStore is enabled, allowBuilds is unset, and dangerouslyAllowAllBuilds is not true. The changeset documents this fix.
GVS build policy precedence tests
config/reader/test/index.ts
Adds test import and two test cases: one verifying workspace manifest allowBuilds overrides global config while preserving dangerouslyAllowAllBuilds, and another verifying global config dangerouslyAllowAllBuilds is preserved in global installs when no workspace manifest exists.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Suggested labels

area: cli/dlx

Suggested reviewers

  • zkochan

Poem

🐰 A config tale of order and care,
Global installs with builds to declare,
Workspace trumps global, with precedence right,
Default allowBuilds now shines in the light!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the main change: fixing global installs to respect build policy from global config.yaml when GVS is enabled.
Linked Issues check ✅ Passed The PR addresses the core coding requirements from issue #9249 by reordering GVS default application and implementing an explicit guard to respect global build policy.
Out of Scope Changes check ✅ Passed All changes in the PR are directly in scope: the changeset documents the fix, the config reader fix implements the reordering and guard logic, and tests verify both scenarios.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@zkochan

zkochan commented May 16, 2026

Copy link
Copy Markdown
Member

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 16, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

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.

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 encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@zkochan zkochan merged commit d3f8408 into pnpm:main May 16, 2026
12 of 13 checks passed
@welcome

welcome Bot commented May 16, 2026

Copy link
Copy Markdown

Congrats on merging your first pull request! 🎉🎉🎉

github-actions Bot pushed a commit to Eyalm321/pnpm that referenced this pull request May 18, 2026
…n GVS is enabled (pnpm#11363)

* fix(config.reader): move GVS allowBuilds default after globalDepsBuildConfig re-application

The GVS default allowBuilds = {} was applied too early — before
workspace manifest settings were read and before .npmrc values
(dangerouslyAllowAllBuilds) were re-applied via globalDepsBuildConfig.

This caused hasDependencyBuildOptions() to return true (because {}} is
not null), blocking restoration of .npmrc values. Global installs
with GVS enabled would silently skip all build scripts even when
the config explicitly allowed them.

This fix moves the GVS default to after both workspace manifest
reading and globalDepsBuildConfig re-application, so that:
1. Workspace manifest allowBuilds takes precedence (if present)
2. .npmrc dangerously-allow-all-builds is properly restored
3. Empty {}} is only applied as a last resort

Closes pnpm#9249

* fix(config.reader): apply Copilot suggestion for GVS allowBuilds guard

From PR review discussion_r3141002317:

- Replace hasDependencyBuildOptions() == null with hasDependencyBuildOptions()
  so the GVS default only applies when no build policy at all is
  configured (not even dangerouslyAllowAllBuilds). This is cleaner because
  the condition now matches the re-application guard on the line
  immediately before it.

- Add regression test verifying that dangerouslyAllowAllBuilds with GVS
  preserves allowBuilds when no global workspace manifest exists.

* docs: update changeset

* fix(config.reader): address PR review feedback

- Fix unreachable GVS allowBuilds default: hasDependencyBuildOptions()
  always returns true after globalDepsBuildConfig re-applies defaults
  (dangerouslyAllowAllBuilds: false is != null). Replace with explicit
  allowBuilds == null && dangerouslyAllowAllBuilds !== true check.
- Rename .npmrc references to global config.yaml in changeset, comments,
  and test names (zkochan: v11 reads from global rc file, not .npmrc).
- Add try/finally env cleanup for XDG_CONFIG_HOME and PNPM_HOME in tests.
- Add test for workspace manifest allowBuilds precedence over config.yaml.

* fix(config.reader): fix GVS workspace manifest test

- Use import.meta.dirname/global/v11 for globalPkgDir (matches env.PNPM_HOME)
- Fix assertion: dangerouslyAllowAllBuilds coexists with allowBuilds
- Clean up global/v11 directory in finally block to prevent test leakage

* fix(config.reader): use object form for workspace manifest allowBuilds; clean up parent global/ dir

Fixes two PR pnpm#11363 review threads:
1. allowBuilds in workspace manifest must be Record<string, boolean>,
   not array — createAllowBuildFunction uses Object.entries()
2. Remove empty config/reader/test/global/ directory after test

* fix(config.reader): address production review nits

- Update changeset: use camelCase dangerouslyAllowAllBuilds (YAML key, not .npmrc)
- Add enableGlobalVirtualStore assertion to first GVS test
- Add comment explaining dangerouslyAllowAllBuilds coexistence on config object

* fix(config.reader): address Copilot review — env safety, GLOBAL_LAYOUT_VERSION, try/finally

- Move XDG_CONFIG_HOME mutation and file setup inside try blocks
  so env is always restored even if setup throws
- Replace hard-coded v11 with GLOBAL_LAYOUT_VERSION import
- Fix corrupted try/finally in workspace manifest precedence test
  (missing finally block and mangled expect line from prior bad edit)
- Reword comment: enableGlobalVirtualStore defaults to true for
  global installs, not \"when not in CI\"

* fix(config.reader): address last 3 Copilot review threads — comment wording, cleanup placement, test rename

* fix(config.reader): fix block-scoped globalDir leak in GVS test

* fix: address Copilot review #4194783789 — restore auth test, fix naming, remove artifacts

* Remove local dev tooling — not part of this PR

* Remove PR.md — issue context is in the PR description

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
Co-authored-by: Tom Hale <tom@hale.net>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

big fan- hardly any issues except this symlink error

3 participants