Skip to content

fix(plugin-npm-cli): enable OIDC publish on CircleCI#7122

Merged
arcanis merged 1 commit intoyarnpkg:masterfrom
joshuayoes:fix-circleci-oidc-publish-gate
May 5, 2026
Merged

fix(plugin-npm-cli): enable OIDC publish on CircleCI#7122
arcanis merged 1 commit intoyarnpkg:masterfrom
joshuayoes:fix-circleci-oidc-publish-gate

Conversation

@joshuayoes
Copy link
Copy Markdown
Contributor

@joshuayoes joshuayoes commented Apr 27, 2026

What's the problem this PR addresses?

yarn npm publish skips the OIDC code path on CircleCI, even though #7075 was supposed to add CircleCI support. That PR updated getOidcToken() in packages/plugin-npm/sources/npmHttpUtils.ts to read NPM_ID_TOKEN when process.env.CIRCLECI is set, but the gating expression in packages/plugin-npm-cli/sources/commands/npm/publish.ts that decides whether to pass allowOidc: true to npmHttpUtils.put was not updated:

allowOidc: Boolean(process.env.CI && (process.env.GITHUB_ACTIONS || process.env.GITLAB_CI)),

So on CircleCI, allowOidc is always false, getOidcToken() is never called, and the publish either fails (no other auth) or falls back to a long-lived token. This contradicts the stated intent of #7075 ("CircleCI was recently added as a supported npm trusted publisher provider, but Yarn's OIDC implementation only supports GitHub Actions and GitLab CI").

Reproduction: a CircleCI release pipeline that mints an OIDC token via circleci run oidc get --claims '{"aud":"npm:registry.npmjs.org"}', exports it as NPM_ID_TOKEN, then runs yarn npm publish against a package configured for trusted publishing. Expected: yarn exchanges the id-token for a single-use publish token. Actual: yarn never attempts the exchange, because the allowOidc gate excludes CircleCI.

Refs #7074, #7075.

How did you fix it?

Added process.env.CIRCLECI to the disjunction, mirroring the branch that #7075 already added in getOidcToken():

allowOidc: Boolean(process.env.CI && (process.env.GITHUB_ACTIONS || process.env.GITLAB_CI || process.env.CIRCLECI)),

This keeps the gate consistent with the providers getOidcToken() actually handles, and matches the same one-line shape as the GitLab fix in #6938.

No new tests: the change is an additional disjunct in a Boolean gate; there were no tests covering the GitHub Actions or GitLab CI cases of this expression either, and #7075 / #6938 were merged without tests for the same reason.

Checklist

  • I have read the Contributing Guide.
  • I have set the packages that need to be released for my changes to be effective.
  • I will check that all automated PR checks pass before the PR gets reviewed.

PR yarnpkg#7075 added CircleCI support to `getOidcToken()` but the gating
expression in `npm publish` was not updated, so the OIDC code path is
still skipped on CircleCI even though `NPM_ID_TOKEN` is available.
joshuayoes added a commit to infinitered/reactotron that referenced this pull request May 1, 2026
## Please verify the following:

- [x] `yarn build-and-test:local` passes
- [ ] I have added tests for any new features, if relevant
- [x] `README.md` (or relevant documentation) has been updated with your
changes

## Describe your PR

Migrates the reactotron CI publish pipeline from classic `NPM_TOKEN`
auth to **npm Trusted Publishing via CircleCI OIDC**. npm GA'd CircleCI
support on
[2026-04-06](https://github.blog/changelog/2026-04-06-npm-trusted-publishing-now-supports-circleci/);
this PR wires us up.

The pipeline has been broken since npm revoked classic tokens on
2025-12-09 and #1602 left the renamed `reactotron-npm-context` with an
empty `NPM_TOKEN`. Rather than mint a granular replacement (capped at 90
days) and rotate forever, OIDC eliminates the human-managed token
entirely.

### Changes

- `.circleci/config.yml` (`release_package` job) β€” replaces the `npm
whoami` + `~/.npmrc` token write with a "Mint npm OIDC token" step using
`circleci run oidc get --claims '{"aud":"npm:registry.npmjs.org"}'`.
- `scripts/release.artifacts.mjs` β€” accepts either `NPM_TOKEN` or
`NPM_ID_TOKEN`, and performs the npm OIDC token exchange directly (`POST
/-/npm/v1/oidc/token/exchange/package/<ident>`). This in-script exchange
is a workaround for
[yarnpkg/berry#7122](yarnpkg/berry#7122): Yarn
4.14.1's `getOidcToken` helper handles CircleCI, but the `allowOidc`
gate in `publish.ts` only flips on for `GITHUB_ACTIONS` / `GITLAB_CI`.
Once 7122 lands and we bump Yarn, the script-level exchange block can be
deleted.
- Yarn `4.1.1 β†’ 4.14.1` (4.14 brought the CircleCI OIDC support that
7122 finishes wiring up).
- `.yarnrc.yml` β€” keeps `npmAuthToken: "${NPM_TOKEN-}"` as a soft
fallback during cutover. Empty string is falsy in Yarn's auth chain, so
it's a no-op when OIDC is in play. Removed in a follow-up PR after first
prod publish.
- `docs/contributing/releasing.md` β€” new "OIDC publish flow" section
covering the CI flow, how to add a trusted publisher when shipping a new
package, and the upstream Yarn workaround.

### Pilot validation

Pilot package: `eslint-plugin-reactotron@0.1.10-beta.0`. Pushed tag from
branch `test/oidc-pilot-eslint`, published under the `beta` dist-tag
(semver prerelease β€” invisible to `^`/`~`/`*` ranges). Pipeline
succeeded; package published.

The npm registry metadata for the pilot version records:

```json
"_npmUser": {
  "name": "CircleCI",
  "email": "npm-oidc-no-reply@github.com",
  "trustedPublisher": {
    "id": "circleci",
    "oidcConfigId": "oidc:71dbce82-a25e-4b17-a0e3-78908cc0e805"
  }
}
```

This is the smoking-gun proof the publish ran via the trusted publisher
path, not legacy token auth. `_npmVersion: null` and `_nodeVersion:
null` (set when the publisher is OIDC) corroborate.

- npm:
https://www.npmjs.com/package/eslint-plugin-reactotron/v/0.1.10-beta.0
- GitHub release:
https://github.com/infinitered/reactotron/releases/tag/eslint-plugin-reactotron%400.1.10-beta.0
- CircleCI pipeline:
https://app.circleci.com/pipelines/gh/infinitered/reactotron/2762

### Pre-merge prerequisites

- βœ… Trusted publisher configured on all 11 npm packages (same Org /
Project / Pipeline Definition / Context / VCS Origin tuple, validated by
the pilot's successful publish).
- βœ… "Publishing access" tightened to **"Require two-factor
authentication and disallow tokens (recommended)"** on all 11 packages.
Per npm's inline note, this is fully OIDC-compatible β€” short-lived OIDC
publish tokens are a separate auth path.

Out of scope: `reactotron-app` (Electron app, GitHub release artifacts
only), `reactotron-mcp` (`"private": true`).

### Follow-up PRs

After **first successful production OIDC publish**:

- Remove `npmAuthToken: "${NPM_TOKEN-}"` from `.yarnrc.yml`.
- Tighten `release.artifacts.mjs` to require only `NPM_ID_TOKEN`.
- Clear `NPM_TOKEN` from CircleCI context (currently absent;
belt-and-suspenders).

After **yarnpkg/berry#7122 lands and we bump Yarn**:

- Delete the OIDC exchange block in `scripts/release.artifacts.mjs` β€”
Yarn handles the exchange directly via `yarn npm publish`.
- Simplify the OIDC docs section.

### Notes

- The chore commit only touches infra files (`.circleci/`,
`.yarnrc.yml`, root `package.json`, `yarn.lock`,
`scripts/release.artifacts.mjs`, `docs/`, `.yarn/releases/`). No `lib/*`
or `apps/*` workspaces affected β†’ 0 release tags created on merge β†’ no
auto-publish triggered. First real OIDC publish happens on the next
legitimate code change inside a `lib/*` workspace.

πŸ€– Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@arcanis arcanis merged commit f6872cf into yarnpkg:master May 5, 2026
26 of 27 checks passed
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.

2 participants