PyColors BlogTechnical article

Fixing npm Trusted Publishing in a pnpm Monorepo

How to fix npm Trusted Publishing failures with GitHub Actions, Changesets, pnpm, and Node.js in a modern Next.js monorepo.

PP

Patrice Parny

Founder of PyColors

May 26, 202614 min read

Why it matters: this article turns a real PyColors product decision into a reusable SaaS implementation pattern.

Hook

I hit one of the most frustrating CI/CD problems while publishing a package from a modern pnpm monorepo.

Everything looked correct.

The workflow passed. OIDC was detected. Changesets worked. The package already existed on npm.

And yet publishing kept failing with this:

E404 Not Found - PUT https://registry.npmjs.org/@pycolors%2ftokens - Not found

Before that, I was also hitting:

EOTP This operation requires a one-time password from your authenticator.

The worst part?

The errors were misleading.

The actual issue was not the package itself.

It was the migration from legacy NPM_TOKEN publishing to npm Trusted Publishing (OIDC) with GitHub Actions.

Here’s the full production-ready setup that finally fixed it.


Context

This happened while publishing packages inside the PyColors ecosystem:

  • Next.js 15
  • pnpm workspaces
  • Turborepo
  • Changesets
  • GitHub Actions
  • npm Trusted Publishing
  • TypeScript
  • Node.js 22

The monorepo contains multiple packages:

@pycolors/ui
@pycolors/tokens
@pycolors/eslint-config
@pycolors/typescript-config

The release pipeline automatically publishes unpublished packages to npm using Changesets.


The Problem

The original workflow used a classic npm token:

env:
  NODE_AUTH_TOKEN: "${{ secrets.NPM_TOKEN }}"

This worked for a while.

Then npm started failing with:

EOTP This operation requires a one-time password from your authenticator.

I switched to npm Trusted Publishing with OIDC.

GitHub Actions then detected OIDC correctly:

No NPM_TOKEN found, but OIDC is available - using npm trusted publishing

But publishing still failed:

E404 Not Found - PUT https://registry.npmjs.org/@pycolors%2ftokens - Not found

The package already existed on npm:

npm view @pycolors/tokens version

# 1.1.0

Local version:

{
  "name": "@pycolors/tokens",
  "version": "1.2.0"
}

So why was npm saying the package was "not found"?


Why This Error Is So Confusing

This is not a normal 404.

With npm Trusted Publishing, npm sometimes returns:

E404

when the OIDC authentication chain is invalid.

That means:

  • wrong workflow filename
  • unsupported Node/npm version
  • Trusted Publisher mismatch
  • workflow not authorized
  • package not linked correctly

The error message hides the real cause.

This makes debugging painful.


Root Cause

I actually had multiple problems at the same time.

1. Wrong Trusted Publisher Workflow Filename

I initially configured:

.github/workflows/release.yml

But npm expects:

release.yml

Only the filename.

Not the full path.


2. Node.js / npm Version Too Old

Trusted Publishing requires recent tooling.

I was using:

node-version: 20

npm Trusted Publishing behaves inconsistently with older npm versions.

After upgrading:

node-version: 22.14.0

and:

- run: npm install -g npm@latest

everything started working correctly.


3. GitHub Workflow Was Ignored

My .gitignore contained this:

.github/
**/.github/

Which meant:

  • workflows were not versioned
  • CI changes were not tracked
  • workflows were inconsistently deployed

This is a terrible idea for modern SaaS infrastructure.


4. Trusted Publisher Was Configured on the Wrong Repository

This one is subtle.

My package repository metadata pointed to:

"repository": {
  "url": "https://github.com/pycolors-io/pycolors-tokens"
}

But the actual publishing workflow runs from:

pycolors-io/pycolors

Trusted Publishing must point to the repository that executes the workflow.

Not the mirror repository.


The Solution

Here’s the final production-ready setup.


Step 1 — Configure npm Trusted Publishing

On npm:

@pycolors/tokens
→ Settings
→ Trusted Publisher

Add:

Organization or user: pycolors-io
Repository: pycolors
Workflow filename: release.yml
Environment: leave empty
Allowed actions: Allow npm publish

Important:

Workflow filename = release.yml

NOT:

.github/workflows/release.yml

Step 2 — Remove Legacy NPM_TOKEN Publishing

Delete this entirely:

NODE_AUTH_TOKEN: "${{ secrets.NPM_TOKEN }}"

Also remove:

- name: "Test npm auth"
  run: npm whoami

OIDC does not use npm tokens.


Step 3 — Enable OIDC Permissions

Your workflow must contain:

permissions:
  contents: write
  pull-requests: write
  id-token: write

The critical permission is:

id-token: write

Without it, OIDC cannot work.


Step 4 — Upgrade Node.js and npm

This part matters more than most developers realize.

Use Node.js 22+:

- name: "Setup Node"
  uses: actions/setup-node@v4
  with:
    node-version: 22.14.0
    registry-url: "https://registry.npmjs.org"
    cache: "pnpm"

Then immediately upgrade npm:

- name: "Upgrade npm"
  run: npm install -g npm@latest

Step 5 — Use Changesets with OIDC

Final publishing step:

- name: "Version or Publish (Changesets)"
  uses: changesets/action@v1
  with:
    version: "pnpm -w changeset version"
    publish: "pnpm -w changeset publish"
    title: "chore(release): version packages"
    commit: "chore(release): version packages"
  env:
    GITHUB_TOKEN: "${{ steps.app-token.outputs.token }}"

No npm token needed.


Final Working Workflow

permissions:
  contents: write
  pull-requests: write
  id-token: write

steps:
  - uses: actions/checkout@v4

  - uses: pnpm/action-setup@v4
    with:
      run_install: false

  - uses: actions/setup-node@v4
    with:
      node-version: 22.14.0
      registry-url: "https://registry.npmjs.org"
      cache: "pnpm"

  - run: npm install -g npm@latest

  - run: pnpm install --frozen-lockfile

  - run: pnpm -w build

  - uses: changesets/action@v1
    with:
      version: "pnpm -w changeset version"
      publish: "pnpm -w changeset publish"

Before / After

Before

  • OTP failures
  • Broken CI publishing
  • Manual npm tokens
  • Misleading E404 errors
  • Security risks
  • Inconsistent workflow tracking

After

  • Zero npm tokens
  • Fully automated publishing
  • Modern OIDC authentication
  • Secure GitHub Actions pipeline
  • Production-ready monorepo releases
  • Cleaner infrastructure

Lessons Learned

1. npm Errors Can Be Extremely Misleading

This:

E404 Not Found

did not actually mean the package was missing.

It meant the OIDC authorization chain was invalid.


2. Modern CI/CD Requires Modern Node/npm Versions

A lot of developers upgrade frameworks but forget infrastructure tooling.

npm Trusted Publishing is evolving quickly.

Old npm versions can fail in strange ways.


3. .github/ Should Usually Be Versioned

Ignoring GitHub workflows is an anti-pattern for modern SaaS teams.

Your CI/CD pipeline is part of your product infrastructure.

Treat it like production code.


4. Monorepo Mirrors Add Complexity

When using public mirrors:

pycolors-tokens
pycolors-ui

you must clearly separate:

  • repository metadata
  • actual publishing repository
  • CI execution source

Otherwise authentication becomes confusing.


SaaS / Builder Insight

This problem is exactly why infrastructure maturity matters in SaaS products.

Most developers think shipping faster only means:

  • UI speed
  • coding faster
  • AI tooling

But operational reliability matters just as much.

Broken release pipelines destroy momentum.

A modern SaaS stack is not just:

  • Next.js
  • Tailwind
  • Prisma
  • Stripe

It’s also:

  • reproducible CI/CD
  • automated publishing
  • secure package distribution
  • scalable monorepo workflows

The teams that ship consistently are the teams that reduce infrastructure friction early.


Upgrade Path

As your SaaS ecosystem grows:

  • multiple packages
  • design systems
  • starters
  • templates
  • shared configs

release automation becomes critical.

This is exactly why production-ready SaaS starters matter.

The difference between:

"it works locally"

and:

"this scales operationally"

is where real engineering leverage happens.

PyColors focuses heavily on this layer:

  • production-ready monorepos
  • modern CI/CD
  • scalable package architecture
  • SaaS infrastructure defaults
  • premium developer workflows

Because shipping is not enough.

You need to keep shipping reliably.

Starter Free

Turn this article into shipping leverage.

Use the reasoning from this article to move faster with the PyColors product path: learn the pattern, validate with Starter Free, and upgrade when implementation wiring becomes the bottleneck.

Read the logic
Validate the surface
Upgrade when ready

Keep exploring

Related articles around the same technical and product surface.

Next.jsFeatured

Tailwind Not Detecting Classes from node_modules? Fix for Next.js + UI Libraries

Fix Tailwind CSS not applying styles from node_modules in Next.js. Learn how to configure content paths for UI libraries.

March 17, 20268 min read
SaaS EngineeringFeatured

Why Production Migrations Break SaaS Products

A real production issue encountered while shipping commerce infrastructure for digital products — and why schema discipline matters more than most developers think.

May 14, 202610 min read
Next.jsFeatured

How to Build Email Verification in Next.js (Auth.js) — Production Guide

Learn how to implement a secure email verification flow in Next.js using Auth.js, Prisma, and Resend. Production-ready approach for SaaS apps.

April 17, 202610 min read
Next step

Build the product surface first. Upgrade when wiring becomes the bottleneck.

Use the blog for product engineering insight, Starter Free for validation, and Starter Pro when real auth, billing, and protected architecture should already be handled.