Skip to content

fix: clear exclusive param siblings when setting from CLI#9023

Open
umeshmore45 wants to merge 2 commits intonpm:latestfrom
umeshmore45:fix/exclusive-params-env-clearing
Open

fix: clear exclusive param siblings when setting from CLI#9023
umeshmore45 wants to merge 2 commits intonpm:latestfrom
umeshmore45:fix/exclusive-params-env-clearing

Conversation

@umeshmore45
Copy link
Contributor

fix: clear exclusive sibling configs from env when one is set via CLI

What's the problem?

If you set an exclusive param via CLI (e.g. --save-prod) but a sibling
(npm_config_save_dev=true) is already in the environment, child processes
inherit both and crash with a conflict. This was also the root cause of the
--min-release-age + --before issue in #9005.

What changed

When setEnvs exports a non-default exclusive config, it now resets that
param's siblings to their defaults in the env — so child processes never
see a conflict. Works generically for all exclusive pairs, not just this one.

Tests

Added a test for the case where save-prod is set via CLI while save-dev
is already in env — verifies save-dev gets reset to its default.

References

Fixes #9005

@umeshmore45 umeshmore45 requested a review from a team as a code owner February 24, 2026 13:44
@owlstronaut
Copy link
Contributor

Thanks for working on this @umeshmore45 — the approach of fixing it generically in set-envs.js for all exclusive params is the right direction per @wraithgar's feedback.

However, the bug is still reproducible with this PR applied. The core issue is what the child process sees when pacote prepares a git dep, which you can test directly:

   npm_config_min_release_age=2 node bin/npm-cli.js install --before=2026-02-23T00:00:00.000Z --dry-run
   # => --min-release-age cannot be provided when using --before

This fails identically with and without the PR. The fix resets npm_config_before="" in the parent's env, but the child's loadEnv skips empty strings, so it has no effect. The actual conflict is between npm_config_min_release_age (still in env) and --before (CLI arg added by pacote) — the fix clears the sibling, but the original config causing the conflict is untouched.

Copy link
Contributor

@owlstronaut owlstronaut left a comment

Choose a reason for hiding this comment

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

~~

@umeshmore45
Copy link
Contributor Author

Thanks for the detailed explanation. I see the distinction now.

My set-envs.js fix clears the sibling (npm_config_before), but the actual problem is npm_config_min_release_age persisting in env when --before is added by pacote as a CLI arg in the child process.

However, I also have a fix in index.js that handles this at load time — when where === 'env', the exclusive check is skipped, so CLI args always take precedence over env configs. This means the child process won't throw even if both are present, because min-release-age comes from env and before comes from CLI.

I tested this with a git dependency (ini: git+https://github.com/npm/ini.git) and the child process installed successfully without any exclusive conflict error.

Could you try reproducing the failure with both changes applied (index.js + set-envs.js)? The index.js change is the one that actually prevents the child process conflict.

for (const exclusive of this.definitions[key].exclusive) {
if (!this.isDefault(exclusive)) {
// when loading from env, skip exclusive check — already-set values take precedence
if (where === 'env') {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this is too broad. it'll silently accept npm_config_before=2026-01-01 npm_config_min_release_age=2 npm install

@wraithgar
Copy link
Member

This is a tricky one because realistically before is the one that is being used in flatOptions, and persisted to submodules. The min release age flag is an attempt at an "alias" which means it should never be exported or imported.

There is a flag that should solve this already: envExport. It's set to false on a few other config items that have aggregation issues or other issues.

Setting it to true for min-release-age may solve this.

@umeshmore45
Copy link
Contributor Author

Hey, quick question on an edge case I ran into while testing.

Right now with my changes, npm_config_min_release_age=2 npm_config_before=2026-01-01 npm install doesn't throw — it silently lets before win since min-release-age converts to before during flatten anyway.

Should this still be an error? If so, I can add a separate validation step post-load rather than catching it during env loading. Let me know how you'd like me to handle this.

Added logic to skip exclusive option checks when an environment variable is set if a sibling option was provided via the command line. This ensures that environment configurations do not conflict with CLI arguments. Additionally, a new test case was introduced to verify this behavior.
@umeshmore45 umeshmore45 force-pushed the fix/exclusive-params-env-clearing branch from 4fcd783 to 9dbc055 Compare February 28, 2026 17:49
@umeshmore45 umeshmore45 requested a review from owlstronaut March 2, 2026 18:35
@owlstronaut
Copy link
Contributor

Yeah, that should still be an error, the user is explicitly setting two conflicting options. The current check is still too broad because this.list[0] has a prototype chain that walks through all config levels, not just CLI.

Refined logic to ensure that exclusive options from the environment are correctly skipped when a sibling option is set via the command line. This change prevents conflicts between environment variables and CLI arguments. Additionally, updated test cases to validate the new behavior and ensure proper functionality.
@umeshmore45
Copy link
Contributor Author

Hey, I’ve updated everything based on your feedback

Changes

  • Using Object.hasOwn(this.data.get('cli').data, exclusive) instead of this.list[0] to avoid prototype chain checks.
  • Switched continue to continue outer so the env entry gets skipped entirely.
  • Added envExport: false to min-release-age as suggested by @wraithgar.

Tested

  • npm_config_min_release_age=2 npm install --before=... → env value skipped
  • npm_config_min_release_age=2 npm_config_before=... npm install → throws as expected
  • Child process (git dep with pacote) → no conflict

Let me know if anything else should be adjusted.

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.

[BUG] min-release-age config error if any dependencies use ~ version range

3 participants