Skip to content

fix(unpublish): refuse to unpublish a version when the registry returns no versions - #10117

Open
meganemura wants to merge 2 commits into
npm:latestfrom
meganemura:fix/unpublish-last-version-guard
Open

meganemura wants to merge 2 commits into
npm:latestfrom
meganemura:fix/unpublish-last-version-guard

Conversation

@meganemura

@meganemura meganemura commented Oct 12, 2026 •

Copy link
Copy Markdown

npm unpublish <pkg>@<version> reads the packument with ?write=true to check the versions. When that packument has no versions, the command passes the check, and libnpmpublish then deletes the whole package instead of the one version. With --force, the same thing happens. A packument with no versions field throws TypeError: Cannot convert undefined or null to object instead.

I saw this through a registry proxy (Aikido safe-chain) that filtered all versions out of the ?write=true reads.

This change:

  • refuses to unpublish a specific version when the registry returns no versions for the package, even with --force;
  • reads a missing versions field as empty instead of throwing.

Unpublishing the whole package (npm unpublish <pkg> --force, <pkg>@*) does not change.

The two new tests (no versions field, and empty versions with --force) fail without the change.

References

Fixes #10116
Related to #4924, #7015

🤖 Generated with Claude Code

`npm unpublish <pkg>@<version>` refuses to delete the last version
without --force, but the check read the abbreviated packument while
libnpmpublish decides from the full one. When the abbreviated document
listed a version the full one did not, the check passed and
libnpmpublish deleted the whole package. A full packument with no
versions also passed the check and deleted the whole package.

Read the full packument for the check, and treat no versions as the
last version.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CykpGv4PTktW9jr8y5EYkR
Comment thread lib/commands/unpublish.js Outdated
Comment thread lib/commands/unpublish.js Outdated

const versions = await Unpublish.getKeysOfVersions(spec.name, opts)
if (versions.length === 1 && spec.rawSpec === versions[0] && !force) {
const lastVersion = versions.length === 0 ||

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

versions length 0 seems like something that shouldn't be happening.

I feel like this should be an unconditional failure.

Curious to get your thoughts @reggi

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

@nkolev92
Agreed. It now fails unconditionally, even with --force, when a version is specified and the registry returns no versions. Whole-package unpublish is unchanged.

…ns no versions

When a specific version is requested and the registry returns a packument
with no versions, libnpmpublish deletes the whole package. Refuse instead,
even with --force, since the requested version cannot be verified.

Drop the fullMetadata read: the full and abbreviated packuments are
expected to agree, and it did not change the outcome.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013XZoh3ni1qhXKcwpBwN5bP
@meganemura meganemura changed the title fix(unpublish): check the last version against the full packument fix(unpublish): refuse to unpublish a version when the registry returns no versions Oct 15, 2026
@meganemura

Copy link
Copy Markdown
Author

I’ve pushed the changes and updated the PR title and description to match.

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 unpublish <pkg>@<version> deleted the whole package without --force

2 participants