Skip to content

npm publish refuses to run outside the release workflow - #13

Merged
las7 merged 1 commit into
mainfrom
harden/publish-only-from-ci
Aug 25, 2026
Merged

npm publish refuses to run outside the release workflow#13
las7 merged 1 commit into
mainfrom
harden/publish-only-from-ci

Conversation

@las7

@las7 las7 commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Closes the hole that burnt v0.4.0.

A release here is two publishes: npm, then the MCP registry. npm publish by hand does only the first, and does it without running the tests — so the registry never sees the version, and the number is spent against the workflow that would have caught whatever is wrong with it.

It already happened

$ npm view @reachpad/mcp@0.3.0 _npmUser.name  →  GitHub Actions
$ npm view @reachpad/mcp@0.3.1 _npmUser.name  →  (a person)
$ npm view @reachpad/mcp@0.4.0 _npmUser.name  →  GitHub Actions
$ npm view @reachpad/mcp@0.4.1 _npmUser.name  →  GitHub Actions

0.3.1 went out by hand on 2026-08-24 carrying a 153-character description, against the registry's 100-character cap. Nothing tried to publish 0.3.1 to the registry, so the cap went unnoticed for three days — until v0.4.0 hit it, after npm had already taken 0.4.0. The listing sat on 0.3.0 the whole time, and 0.4.0 is on npm and absent from the registry permanently.

The gate

prepublishOnly refuses unless CI is set. It costs nothing, needs no credential to check, and fails toward a refused publish rather than a bad one. The message says what to do instead:

publish-guard: REFUSING to publish from outside CI.

  Release by pushing a tag instead:

      git tag -a v<version> <sha> -m "<what changed>"
      git push origin v<version>

  That runs the tests, publishes to npm, and publishes to the registry.

  If you really mean to do it by hand: ALLOW_MANUAL_PUBLISH=1 npm publish

The escape hatch is deliberate, explicit and greppable, and warns that the registry half will not run.

Verified by running it, not by reading it

case result
no CI refused, exit 1
CI=true packs, exit 0 — the workflow is unaffected
ALLOW_MANUAL_PUBLISH=1 allowed with the warning, exit 0
npm pack the guard does not ship in the tarball

48 tests pass, 1 skipped (live, needs credentials).

Still worth doing separately

This is a client-side gate — it stops the accident, not a determined publish. The registry-side version is npm trusted publishing (require a provenance-signed CI publish, reject token publishes). That needs npm account access I don't have from this box.

🤖 Generated with Claude Code

https://claude.ai/code/session_01STcKsJb8PNq3xbYsCuiiHs

A release here is TWO publishes: npm, then the MCP registry. `npm publish` by
hand does only the first, and does it without running the tests -- so the
registry never sees the version, and the number is spent, and it is spent
against the workflow that would have caught whatever is wrong.

That is not hypothetical. 0.3.1 went out by hand on 2026-08-24 --
`npm view @reachpad/mcp@0.3.1 _npmUser.name` names a person where every other
release says `GitHub Actions`. It carried a 153-character description against
the registry's 100-character cap. Nothing ever tried to publish 0.3.1 to the
registry, so the cap went unnoticed for three days until v0.4.0 hit it, AFTER
npm had taken 0.4.0. The listing sat on 0.3.0 the whole time and 0.4.0 is on
npm and absent from the registry permanently.

`prepublishOnly` now refuses unless `CI` is set. That gate costs nothing,
needs no credential to check, and fails toward a refused publish rather than a
bad one. The message says what to do instead: push the tag.

Escape hatch is explicit and greppable -- `ALLOW_MANUAL_PUBLISH=1 npm publish`
-- and warns that the registry half will not run.

Four behaviours verified by running them, not by reading the script:
  no CI                  -> refused, exit 1
  CI=true                -> packs, exit 0 (the workflow is unaffected)
  ALLOW_MANUAL_PUBLISH=1 -> allowed with the warning, exit 0
  npm pack               -> the guard does NOT ship in the tarball

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STcKsJb8PNq3xbYsCuiiHs
@las7
las7 merged commit 241a733 into main Aug 25, 2026
1 check passed
@las7
las7 deleted the harden/publish-only-from-ci branch August 25, 2026 19:50
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.

1 participant