Skip to content

fix(config): allow setting a string option to an empty value - #9963

Open
SomSamantray wants to merge 1 commit into
npm:latestfrom
SomSamantray:fix/config-set-empty-value
Open

fix(config): allow setting a string option to an empty value#9963
SomSamantray wants to merge 1 commit into
npm:latestfrom
SomSamantray:fix/config-set-empty-value

Conversation

@SomSamantray

Copy link
Copy Markdown

npm config set <key> "" silently deleted the key instead of persisting an empty
string, because keyValues() collapsed "no value given" and "explicitly empty
value" into the same '' fallback, and set() mapped '' to config.delete().

This made the documented tag-version-prefix="" workflow unreachable from the CLI:
config options that are meaningful when empty could not be set to empty. It has
been broken since npm v9.6.5, when the val === '' delete branch was introduced
alongside the matching docs sentence.

What this changes

lib/commands/config.js now uses a null sentinel for a value that was omitted
entirely, so the two intents stay distinct:

  • npm config set <key> — value omitted, the key is removed (unchanged)
  • npm config set <key> "" — an explicit empty string, persisted as <key>=

An empty value is only persisted for options whose type is String, since that is
the only type for which an empty string is a real value. For every other type an
empty value keeps removing the key, which is exactly what it did before this
change. This matters because config parsing coerces an empty string to true for
Boolean options and to 0 for Number options, and rejects it for URL and enum
options — so persisting it generally would mean npm config set save-exact ""
silently enables save-exact, and npm config set registry "$UNSET_VAR" would
write registry= and make every later npm command fail with ERR_INVALID_URL.
Keys with no definition, such as a nerf-darted credential, also keep deleting.

The delete branch keys off whether the value is usable rather than whether it is
literally empty, and the debug log line still reports "" for an omitted value so
existing verbose output is unchanged. An empty value was already handled correctly
everywhere downstream — this only fixes the path that never let one through.

One small semantic consequence worth flagging: values are trimmed as before, so
npm config set tag-version-prefix " " is treated as an explicit empty value for
a string option and now persists the empty string instead of deleting the key. For
non-string options a whitespace-only value still deletes, as it did before. Happy to
change this if you would rather a whitespace-only value keep meaning "remove".

Verification

npm config set tag-version-prefix "" followed by npm version patch in a git
repo now prints 1.0.1 and creates the 1.0.1 git tag and commit message, with no
v prefix. npm config set tag-version-prefix still removes the key and
npm config get falls back to the v default.

Typed options are unchanged: after npm config set save-exact "",
npm config get save-exact is still false (not true); fetch-retries stays at
its default 2 (not 0); and registry keeps its default with subsequent
commands working normally.

  • npx tap test/lib/commands/config.js --no-coverage — pass
  • node . run eslint — pass
  • node . run test — pass

The tests cover the explicit empty value, the key= form, a mixed invocation that
pairs an empty value with an omitted key, and one case per non-string option type
(Boolean, Number, URL, enum); the existing omitted-value deletion test still
passes unchanged.

References

Fixes #6719

`npm config set <key> ""` deleted the key instead of persisting an empty
string, because `keyValues()` collapsed "no value given" and "explicitly
empty value" into the same `''` fallback and `set()` mapped `''` to
`config.delete()`. Options that are meaningful when empty, such as
`tag-version-prefix`, could not be set to an empty value from the CLI.

Use a `null` sentinel for a value that was omitted entirely so the two
intents stay distinct. An explicit empty value is persisted only for
options whose type is `String`, since that is the only type for which an
empty string is a real value. For every other type an empty value keeps
removing the key, as it did before, because parsing coerces it to `true`
for `Boolean` options, to `0` for `Number` options, and rejects it for URL
and enum options. Keys without a definition, such as a nerf-darted
credential, also keep removing the key.
@SomSamantray
SomSamantray requested a review from a team as a code owner September 11, 2026 20:06
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] npm version command does not respect config set tag-version-prefix "" since >= v9.6.5

1 participant