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

Merged
owlstronaut merged 2 commits into
npm:latestfrom
umeshmore45:fix/exclusive-params-env-clearing
Mar 18, 2026
Merged

fix: clear exclusive param siblings when setting from CLI#9023
owlstronaut merged 2 commits into
npm:latestfrom
umeshmore45:fix/exclusive-params-env-clearing

Conversation

@umeshmore45

Copy link
Copy Markdown
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 ownerFebruary 24, 2026 13:44
@owlstronaut

Copy link
Copy Markdown

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.

@owlstronautowlstronaut 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.

~~

@umeshmore45

Copy link
Copy Markdown
ContributorAuthor

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
Copy Markdown

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
Copy Markdown
Contributor

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
Copy Markdown
ContributorAuthor

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
umeshmore45force-pushed the fix/exclusive-params-env-clearing branch from 4fcd783 to 9dbc055CompareFebruary 28, 2026 17:49
@owlstronaut

Copy link
Copy Markdown

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
Copy Markdown
ContributorAuthor

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.

@owlstronautowlstronaut 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.

@umeshmore45 this looks great. Thank you for putting the work in and incorporating our feedbacks!

@owlstronaut
owlstronaut merged commit e839b07 into npm:latestMar 18, 2026
16 checks passed
@github-actionsgithub-actionsBot mentioned this pull request Mar 18, 2026
@github-actionsgithub-actionsBot mentioned this pull request May 27, 2026
Sign up for freeto 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

5 participants

@umeshmore45@owlstronaut@wraithgar@Tanker187@umesh-more-cstk
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

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

Merged
owlstronaut merged 2 commits into
npm:latestfrom
umeshmore45:fix/exclusive-params-env-clearing
Mar 18, 2026
Merged

fix: clear exclusive param siblings when setting from CLI#9023
owlstronaut merged 2 commits into
npm:latestfrom
umeshmore45:fix/exclusive-params-env-clearing

Conversation

@umeshmore45

Copy link
Copy Markdown
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 ownerFebruary 24, 2026 13:44
@owlstronaut

Copy link
Copy Markdown

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.

@owlstronautowlstronaut 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.

~~

@umeshmore45

Copy link
Copy Markdown
ContributorAuthor

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
Copy Markdown

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
Copy Markdown
Contributor

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
Copy Markdown
ContributorAuthor

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
umeshmore45force-pushed the fix/exclusive-params-env-clearing branch from 4fcd783 to 9dbc055CompareFebruary 28, 2026 17:49
@owlstronaut

Copy link
Copy Markdown

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
Copy Markdown
ContributorAuthor

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.

@owlstronautowlstronaut 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.

@umeshmore45 this looks great. Thank you for putting the work in and incorporating our feedbacks!

@owlstronaut
owlstronaut merged commit e839b07 into npm:latestMar 18, 2026
16 checks passed
@github-actionsgithub-actionsBot mentioned this pull request Mar 18, 2026
@github-actionsgithub-actionsBot mentioned this pull request May 27, 2026
Sign up for freeto 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

5 participants

@umeshmore45@owlstronaut@wraithgar@Tanker187@umesh-more-cstk
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

Merged
owlstronaut merged 2 commits into
npm:latestfrom
umeshmore45:fix/exclusive-params-env-clearing
Mar 18, 2026
Merged

fix: clear exclusive param siblings when setting from CLI#9023
owlstronaut merged 2 commits into
npm:latestfrom
umeshmore45:fix/exclusive-params-env-clearing

Conversation

@umeshmore45

Copy link
Copy Markdown
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 ownerFebruary 24, 2026 13:44
@owlstronaut

Copy link
Copy Markdown

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.

@owlstronautowlstronaut 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.

~~

@umeshmore45

Copy link
Copy Markdown
ContributorAuthor

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
Copy Markdown

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
Copy Markdown
Contributor

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
Copy Markdown
ContributorAuthor

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
umeshmore45force-pushed the fix/exclusive-params-env-clearing branch from 4fcd783 to 9dbc055CompareFebruary 28, 2026 17:49
@owlstronaut

Copy link
Copy Markdown

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
Copy Markdown
ContributorAuthor

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.

@owlstronautowlstronaut 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.

@umeshmore45 this looks great. Thank you for putting the work in and incorporating our feedbacks!

@owlstronaut
owlstronaut merged commit e839b07 into npm:latestMar 18, 2026
16 checks passed
@github-actionsgithub-actionsBot mentioned this pull request Mar 18, 2026
@github-actionsgithub-actionsBot mentioned this pull request May 27, 2026
Sign up for freeto 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

5 participants

@umeshmore45@owlstronaut@wraithgar@Tanker187@umesh-more-cstk
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

Merged
owlstronaut merged 2 commits into
npm:latestfrom
umeshmore45:fix/exclusive-params-env-clearing
Mar 18, 2026
Merged

fix: clear exclusive param siblings when setting from CLI#9023
owlstronaut merged 2 commits into
npm:latestfrom
umeshmore45:fix/exclusive-params-env-clearing

Conversation

@umeshmore45

Copy link
Copy Markdown
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 ownerFebruary 24, 2026 13:44
@owlstronaut

Copy link
Copy Markdown

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.

@owlstronautowlstronaut 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.

~~

@umeshmore45

Copy link
Copy Markdown
ContributorAuthor

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
Copy Markdown

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
Copy Markdown
Contributor

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
Copy Markdown
ContributorAuthor

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
umeshmore45force-pushed the fix/exclusive-params-env-clearing branch from 4fcd783 to 9dbc055CompareFebruary 28, 2026 17:49
@owlstronaut

Copy link
Copy Markdown

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
Copy Markdown
ContributorAuthor

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.

@owlstronautowlstronaut 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.

@umeshmore45 this looks great. Thank you for putting the work in and incorporating our feedbacks!

@owlstronaut
owlstronaut merged commit e839b07 into npm:latestMar 18, 2026
16 checks passed
@github-actionsgithub-actionsBot mentioned this pull request Mar 18, 2026
@github-actionsgithub-actionsBot mentioned this pull request May 27, 2026
Sign up for freeto 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

5 participants

@umeshmore45@owlstronaut@wraithgar@Tanker187@umesh-more-cstk
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

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

Merged
owlstronaut merged 2 commits into
npm:latestfrom
umeshmore45:fix/exclusive-params-env-clearing
Mar 18, 2026
Merged

fix: clear exclusive param siblings when setting from CLI#9023
owlstronaut merged 2 commits into
npm:latestfrom
umeshmore45:fix/exclusive-params-env-clearing

Conversation

@umeshmore45

Copy link
Copy Markdown
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 ownerFebruary 24, 2026 13:44
@owlstronaut

Copy link
Copy Markdown

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.

@owlstronautowlstronaut 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.

~~

@umeshmore45

Copy link
Copy Markdown
ContributorAuthor

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
Copy Markdown

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
Copy Markdown
Contributor

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
Copy Markdown
ContributorAuthor

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
umeshmore45force-pushed the fix/exclusive-params-env-clearing branch from 4fcd783 to 9dbc055CompareFebruary 28, 2026 17:49
@owlstronaut

Copy link
Copy Markdown

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
Copy Markdown
ContributorAuthor

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.

@owlstronautowlstronaut 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.

@umeshmore45 this looks great. Thank you for putting the work in and incorporating our feedbacks!

@owlstronaut
owlstronaut merged commit e839b07 into npm:latestMar 18, 2026
16 checks passed
@github-actionsgithub-actionsBot mentioned this pull request Mar 18, 2026
@github-actionsgithub-actionsBot mentioned this pull request May 27, 2026
Sign up for freeto 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

5 participants

@umeshmore45@owlstronaut@wraithgar@Tanker187@umesh-more-cstk
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

Merged
owlstronaut merged 2 commits into
npm:latestfrom
umeshmore45:fix/exclusive-params-env-clearing
Mar 18, 2026
Merged

fix: clear exclusive param siblings when setting from CLI#9023
owlstronaut merged 2 commits into
npm:latestfrom
umeshmore45:fix/exclusive-params-env-clearing

Conversation

@umeshmore45

Copy link
Copy Markdown
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 ownerFebruary 24, 2026 13:44
@owlstronaut

Copy link
Copy Markdown

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.

@owlstronautowlstronaut 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.

~~

@umeshmore45

Copy link
Copy Markdown
ContributorAuthor

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
Copy Markdown

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
Copy Markdown
Contributor

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
Copy Markdown
ContributorAuthor

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
umeshmore45force-pushed the fix/exclusive-params-env-clearing branch from 4fcd783 to 9dbc055CompareFebruary 28, 2026 17:49
@owlstronaut

Copy link
Copy Markdown

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
Copy Markdown
ContributorAuthor

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.

@owlstronautowlstronaut 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.

@umeshmore45 this looks great. Thank you for putting the work in and incorporating our feedbacks!

@owlstronaut
owlstronaut merged commit e839b07 into npm:latestMar 18, 2026
16 checks passed
@github-actionsgithub-actionsBot mentioned this pull request Mar 18, 2026
@github-actionsgithub-actionsBot mentioned this pull request May 27, 2026
Sign up for freeto 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

5 participants

@umeshmore45@owlstronaut@wraithgar@Tanker187@umesh-more-cstk
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

Merged
owlstronaut merged 2 commits into
npm:latestfrom
umeshmore45:fix/exclusive-params-env-clearing
Mar 18, 2026
Merged

fix: clear exclusive param siblings when setting from CLI#9023
owlstronaut merged 2 commits into
npm:latestfrom
umeshmore45:fix/exclusive-params-env-clearing

Conversation

@umeshmore45

Copy link
Copy Markdown
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 ownerFebruary 24, 2026 13:44
@owlstronaut

Copy link
Copy Markdown

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.

@owlstronautowlstronaut 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.

~~

@umeshmore45

Copy link
Copy Markdown
ContributorAuthor

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
Copy Markdown

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
Copy Markdown
Contributor

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
Copy Markdown
ContributorAuthor

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
umeshmore45force-pushed the fix/exclusive-params-env-clearing branch from 4fcd783 to 9dbc055CompareFebruary 28, 2026 17:49
@owlstronaut

Copy link
Copy Markdown

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
Copy Markdown
ContributorAuthor

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.

@owlstronautowlstronaut 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.

@umeshmore45 this looks great. Thank you for putting the work in and incorporating our feedbacks!

@owlstronaut
owlstronaut merged commit e839b07 into npm:latestMar 18, 2026
16 checks passed
@github-actionsgithub-actionsBot mentioned this pull request Mar 18, 2026
@github-actionsgithub-actionsBot mentioned this pull request May 27, 2026
Sign up for freeto 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

5 participants

@umeshmore45@owlstronaut@wraithgar@Tanker187@umesh-more-cstk
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

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

Merged
owlstronaut merged 2 commits into
npm:latestfrom
umeshmore45:fix/exclusive-params-env-clearing
Mar 18, 2026
Merged

fix: clear exclusive param siblings when setting from CLI#9023
owlstronaut merged 2 commits into
npm:latestfrom
umeshmore45:fix/exclusive-params-env-clearing

Conversation

@umeshmore45

Copy link
Copy Markdown
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 ownerFebruary 24, 2026 13:44
@owlstronaut

Copy link
Copy Markdown

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.

@owlstronautowlstronaut 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.

~~

@umeshmore45

Copy link
Copy Markdown
ContributorAuthor

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
Copy Markdown

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
Copy Markdown
Contributor

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
Copy Markdown
ContributorAuthor

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
umeshmore45force-pushed the fix/exclusive-params-env-clearing branch from 4fcd783 to 9dbc055CompareFebruary 28, 2026 17:49
@owlstronaut

Copy link
Copy Markdown

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
Copy Markdown
ContributorAuthor

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.

@owlstronautowlstronaut 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.

@umeshmore45 this looks great. Thank you for putting the work in and incorporating our feedbacks!

@owlstronaut
owlstronaut merged commit e839b07 into npm:latestMar 18, 2026
16 checks passed
@github-actionsgithub-actionsBot mentioned this pull request Mar 18, 2026
@github-actionsgithub-actionsBot mentioned this pull request May 27, 2026
Sign up for freeto 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

5 participants

@umeshmore45@owlstronaut@wraithgar@Tanker187@umesh-more-cstk