Skip to content

Publish to npm on tag push via trusted publishing (OIDC)#1048

Open
dev-milos wants to merge 3 commits into
G-Research:masterfrom
dev-milos:ci/release-publish-workflow
Open

Publish to npm on tag push via trusted publishing (OIDC)#1048
dev-milos wants to merge 3 commits into
G-Research:masterfrom
dev-milos:ci/release-publish-workflow

Conversation

@dev-milos

@dev-milos dev-milos commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Publishes the root bobbit package to npm on a v* tag push, using trusted publishing (OIDC) — no NPM_TOKEN, no OTP, provenance generated automatically. Mirrors the approach validated in gr-oss-developers/trusted-publishing-demo.

Changes

  • .github/workflows/release-publish.yml — on push: tags: 'v*': guard that the tag matches package.json version → npm cinpm publish --provenance. Node 24 (npm ≥ 11.5.1 for OIDC); the build runs via the existing prepublishOnly hook. Pre-release tags (v1.0.0-rc.1) publish to the next dist-tag; stable tags to latest.
  • package.json — adds repository/homepage/bugs. repository is required for provenance and was missing, which is why the old manual --provenance never produced an attestation.
  • Runbook (SKILL.md, docs/releasing.md) — the human bumps the version, writes notes, signs and pushes the tag; the tag push triggers the publish. npm login/OTP now applies only to the manual binary sub-package publishes.

Scope: root package only; sub-packages, version bump, notes, signed tag, and the GitHub Release stay as they are.

To activate (npm admin, not in this PR)

Configure the Trusted Publisher on npmjs.com → bobbit → org G-Research, repo bobbit, workflow release-publish.yml. bobbit already exists on npm (0.14.2), so no manual first publish is needed. The workflow filename must match the npm config exactly.

Validation

actionlint clean, npm publish --dry-run packs on Node 24, npm ci in sync. The real OIDC publish can't be tested from a fork (the trusted-publisher binding is scoped to G-Research/bobbit) — the first real tag is the live test, recoverable by re-running the workflow for the same version.

Adds .github/workflows/release-publish.yml: on a v* tag push it builds and
publishes the root bobbit package to npm using trusted publishing (OIDC) with
automatic provenance -- no NPM_TOKEN, no OTP. Mirrors the validated
gr-oss-developers/trusted-publishing-demo workflow (top-level id-token: write,
Node 24 for npm >= 11.5.1, npm publish --provenance), plus the two things
bobbit needs that the demo doesn't: npm ci (bobbit has deps; the build runs via
the existing prepublishOnly hook) and a tag-vs-package.json-version guard.

package.json gains repository/homepage/bugs. repository is required for
provenance -- its URL must resolve to the building repo -- and was absent, which
is why the runbook's manual `npm publish --provenance` never actually produced
an attestation.

Runbook (SKILL.md + docs/releasing.md) now delegates the root publish to CI:
the human bumps the version, writes notes, signs the tag, and pushes it; the
tag push triggers the publish. npm login/OTP is scoped to the binary
sub-package republish case (still manual). Also fixes a stale SuuBro/bobbit
reference to G-Research/bobbit.

The root publish is not exercisable from a fork -- the npm trusted-publisher is
bound to the G-Research/bobbit repo identity. Validated here: actionlint clean,
npm publish --dry-run packs on Node 24, guard logic, and the JSON/YAML parse.
Manual prerequisites (npm admin, not code): configure the trusted publisher on
npmjs.com for bobbit -> G-Research/bobbit -> release-publish.yml. bobbit already
exists on npm (0.14.2), so no manual first publish is needed.
A v* tag with a semver pre-release suffix (v1.0.0-rc.1, v0.15.0-beta) now
publishes to the `next` dist-tag instead of `latest`, so a pre-release cannot
become the version `npm install bobbit` resolves. Stable tags still go to
`latest`.
Publish under the @gresearch npm org: sets name to @gresearch/bobbit and adds
publishConfig.access public (scoped packages default to restricted). The bin
command stays `bobbit`. This is a new scoped package on npm, so its first
publish is a manual bootstrap before trusted publishing takes over.
@dev-milos
dev-milos marked this pull request as ready for review July 24, 2026 11:42
@dev-milos
dev-milos requested a review from SuuBro as a code owner July 24, 2026 11:42
@greptile-apps

greptile-apps Bot commented Jul 24, 2026

Copy link
Copy Markdown

Greptile Summary

Automates npm publication when a version tag is pushed.

  • Adds an OIDC-enabled GitHub Actions workflow with version validation and prerelease dist-tag routing.
  • Adds npm repository metadata and changes the root package identity to @gresearch/bobbit.
  • Updates the release runbooks so root publication occurs in CI while binary packages remain manual.

Confidence Score: 2/5

This PR should not merge until the npm package identity is corrected and the publishing workflow enforces the approved release boundary.

The workflow publishes the package identity declared in package.json, which now differs from the existing package and trusted-publisher target, and any qualifying upstream tag can trigger OIDC publication without validating its signature or approved commit ancestry.

package.json, .github/workflows/release-publish.yml

Security Review

The tag-triggered workflow treats any matching upstream v* tag as publish authorization. It does not enforce the documented signed-tag requirement or verify that the tagged commit is the approved merge commit on master, allowing a collaborator with tag-push access to publish unreviewed code through the npm OIDC identity.

Important Files Changed

Filename Overview
.github/workflows/release-publish.yml Adds tag-triggered OIDC publication but does not verify that the tag or tagged commit passed the documented release approval process.
package.json Adds provenance metadata and public access configuration but changes the package identity away from the existing bobbit package.
docs/releasing.md Documents CI publication of the root package and manual binary publication ordering.
.claude/skills/release/SKILL.md Updates the release automation instructions to push the tag before monitoring the CI-backed root publish.

Sequence Diagram

sequenceDiagram
    participant M as Maintainer
    participant GH as GitHub Actions
    participant N as npm Registry
    M->>GH: "Push v* tag"
    GH->>GH: Verify tag equals package.json version
    GH->>GH: npm ci and prepublishOnly build
    GH->>N: OIDC npm publish with provenance
    N-->>GH: Publish latest or next dist-tag
Loading

Reviews (1): Last reviewed commit: "Scope the package as @gresearch/bobbit" | Re-trigger Greptile

Comment thread package.json
@@ -1,7 +1,18 @@
{
"name": "bobbit",
"name": "@gresearch/bobbit",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Package identity targets wrong release

When a maintainer pushes a release tag, npm publish reads this renamed manifest identity and targets @gresearch/bobbit instead of the existing bobbit package, leaving the intended release unpublished and conflicting with its trusted-publisher binding.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@SuuBro i think this is fine. correct?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The package identity change is intentional. This is the first phase of a migration from bobbit to @gresearch/bobbit; the unscoped package will remain available during a transition period. This PR is not intended to dual-publish both identities.

The agreed activation sequence is:

  1. Merge this PR.
  2. Manually bootstrap @gresearch/bobbit.
  3. Configure its npm trusted-publisher binding.
  4. Only then push the next release tag.

Before merging, we should make the PR consistent with those assumptions:

  • Update the PR description to identify @gresearch/bobbit as the target, document the post-merge bootstrap as the one-time exception to normal CI-only publishing, and state that the next release tag follows bootstrap and OIDC configuration.
  • Regenerate package-lock.json with the scoped package name.
  • Update release verification and reporting to install/import/link to @gresearch/bobbit.

User-facing installation instructions can continue referencing bobbit until the later migration phase. With these changes, the scoped package identity is the intended release target rather than an accidental one.

Comment on lines +4 to +6
push:
tags:
- 'v*'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 security Tag push bypasses release approval

When a collaborator able to push upstream tags creates a matching v* tag on arbitrary code, this workflow checks out that commit and obtains the npm OIDC identity without verifying tag signatures, master ancestry, or the approved merge commit, causing unreviewed package contents to be published.

@pavlovic-ivan pavlovic-ivan Jul 24, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@SuuBro @dev-milos We can add a ruleset that protects the tags a bit later, once automated fashion of creating tags is implemented. Or, again add a bypass to who can create a tag in the very same ruleset

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.

3 participants