Skip to content

fix(publish): dispatch experimental builds instead of publishing from a PR - #204

Merged
derrickmehaffy merged 1 commit into
mainfrom
fix/experimental-dispatch-bridge
Aug 14, 2026
Merged

fix(publish): dispatch experimental builds instead of publishing from a PR#204
derrickmehaffy merged 1 commit into
mainfrom
fix/experimental-dispatch-bridge

Conversation

@derrickmehaffy

Copy link
Copy Markdown
Contributor

Root cause, proven

npm's trusted publishing does not authenticate a run triggered by
pull_request_target
.

Everything else in that run worked — tarball read, version stamped correctly,
provenance signed and logged to Sigstore — and only the registry write was
refused:

npm error 404 Not Found - PUT https://registry.npmjs.org/@strapi-community%2fplugin-rest-cache

which reads as "no such package" and means "these credentials are not accepted".

The identical code, dispatched on main, published all three packages first
try (run 31761996762):

experimental: 0.0.0-experimental.466acb190b5d2d2c837922466b0ed92e98c29d2c
plugin-rest-cache            ✓
provider-rest-cache-memory   ✓
provider-rest-cache-redis    ✓

latest untouched at 5.0.1. Matches npm/cli#8739.

Ruled out first, each verified rather than assumed: trusted publisher config
(saved, correct), environment name (blank — correct for three modes), npm
version (11.16.0 vs 11.5.1 floor), and repository.url (exact match on all
three packages).

Fix

publish.yml loses its pull_request_target trigger. experimental-dispatch.yml
turns the label into a dispatch of it, so the run that reaches npm is always a
workflow_dispatch on the default branch.

GITHUB_TOKEN is sufficient — workflow_dispatch and repository_dispatch are
the documented exceptions
to the rule that a GITHUB_TOKEN-triggered event starts no new run. No PAT.

Unchanged from your point of view: add the label, get a build. The bridge
passes the PR's exact head sha, publish.yml comments the install command back
on the PR, and the label is removed so a second build is a second deliberate
labelling.

The bridge stays on pull_request_target for the original reason — pull_request
would run the PR branch's copy of the guard below — and the usual footgun does
not apply, because it checks out nothing at all.

Latent bug the dispatch test exposed

The experimental version was stamped from github.sha. This workflow always
runs from the default branch, so that is the tip of mainnot the commit
checked out. Dispatching against any other branch would have built that branch
and stamped it with main's sha, naming a commit that was never built. It only
looked right in the test because target_branch was main.

The sha now comes from git rev-parse HEAD in the checkout.

Why not Strapi's branch dropdown

Strapi publishes straight from a pull_request and lets you pick any branch in
"Use workflow from". They can: they authenticate with a stored NPM_TOKEN, and
their publish job has no environment gate.

Here, npm-experimental has a deployment branch policy allowing only main, so
selecting a feature branch would be blocked before the job runs. Loosening it
would mean the publish job executes that branch's publish.yml — and since
npm discards the ref, that branch could strip the version-shape assertion and
publish to latest. That is a review bypass straight to the latest tag.

Note

Touches workflows, so it needs a maintainer to merge — and pull_request_target
runs the default branch's copy, so the label will not work until this is on
main.

🤖 Generated with Claude Code

… a PR

npm's trusted publishing does not authenticate a run triggered by
pull_request_target. Everything else in that run works - the tarball is read,
the version stamps correctly, provenance is signed and logged to Sigstore - and
only the registry write is refused, with a 404 on the PUT that reads as "no
such package" and means "these credentials are not accepted". The identical
code dispatched on main published all three packages first try.
See npm/cli#8739.

So publish.yml loses its pull_request_target trigger, and experimental-dispatch.yml
turns the label into a dispatch of it. The run that reaches npm is now always a
workflow_dispatch on the default branch. GITHUB_TOKEN can do this:
workflow_dispatch is a documented exception to the rule that a
GITHUB_TOKEN-triggered event starts no new run.

The bridge stays on pull_request_target for the original reason - pull_request
would run the PR branch's copy of the guard - and the usual footgun does not
apply because it checks out nothing.

Also fixes a latent bug the dispatch test exposed: the version was stamped from
github.sha, which is the tip of main, not the commit checked out. Dispatching
against any other branch would have built that branch and stamped it with
main's sha, naming a commit that was never built. The sha now comes from
git rev-parse HEAD in the checkout.

Strapi publishes straight from a pull_request, which works because it
authenticates with a stored NPM_TOKEN rather than OIDC, and its publish job has
no environment gate. That is the trade being declined here.

Co-Authored-By: Claude <noreply@anthropic.com>
@derrickmehaffy
derrickmehaffy merged commit 586e59d into main Aug 14, 2026
12 checks passed
This was referenced Aug 14, 2026
@derrickmehaffy
derrickmehaffy deleted the fix/experimental-dispatch-bridge branch August 14, 2026 05:46
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