Skip to content

Release publish dies before the registry: nested pnpm hands npm a self-colliding config (userconfig == globalconfig) #10146

Description

@os-elon

What happened

Run 32355381481 (ReleasePublish to npm + push version tags, 17.5.0) failed after a full green build:

🦋 changeset v3.0.0
Received an unexpected error for @objectstack/account: (no code)
Exit prior to config file resolving
cause
double-loading config "/home/runner/.config/pnpm/rc" as "global", previously loaded as "user"
🦋 Exited with code 1

Nothing was published; the workspace is untouched. @objectstack/account is just the first package to report — every package fails identically.

Root cause

Three layers stack up, and only the third is new:

  1. pnpm run release exports npm_config_globalconfig=~/.config/pnpm/rc to every child process. pnpm forces this into its rawConfig unconditionally ({ globalconfig: path.join(configDir, 'rc') }).
  2. changeset publish v3 detects the pnpm workspace and shells out to pnpm info / pnpm publish per package — where v2 always shelled out to npm. So the publish now runs a nested pnpm underneath pnpm run.
  3. That nested pnpm reads the inherited npm_config_globalconfig back in, and when it delegates info to npm it hands the npm child bothnpm_config_userconfigandnpm_config_globalconfig pointing at that one file. npm refuses to start when its "user" and "global" config resolve to the same path — it aborts in @npmcli/config before it parses argv, which is why the error arrives with no npm error code and changesets prints (no code).

Upstream: pnpm/pnpm#10914 — "pnpm 10.31.0 breaks nested pnpm invocations under pnpm run", closed as a duplicate of #10926, unfixed on the 10.31.0 line we pin.

Why 2026-08-14's release (run 31781157571) was green on the same pnpm pin: it ran @changesets/cli@^2.31.1, whose npm-only spawn saw userconfig=$HOME/.npmrc and globalconfig=~/.config/pnpm/rc — two different files, no collision. The move to @changesets/cli@^3.0.0 is what routed the publish through a nested pnpm.

Reproduction (pnpm 10.31.0, verbatim)

// package.json
{ "packageManager": "pnpm@10.31.0", "scripts": { "repro": "pnpm info react --json" } }
$ pnpm run repro
Exit prior to config file resolving
cause
double-loading config "/root/.config/pnpm/rc" as "global", previously loaded as "user"

pnpm info react --json on its own (not under pnpm run) succeeds — the nesting is the trigger.

Fix

Drop the inherited value in scripts/release-publish.sh before changeset publish:

unset npm_config_globalconfig NPM_CONFIG_GLOBALCONFIG

Verified against the reproduction above: with the unset, the nested pnpm info returns registry JSON. pnpm recomputes its own global config path from configDir either way, so nothing pnpm needs is lost, and npm falls back to its own default global config while keeping $HOME/.npmrc — where the workflow writes NPM_TOKEN — as the user config. pnpm publish's npm child was already passed only globalconfig, never userconfig, so auth resolution is unchanged.

Two alternatives were tested and rejected: forcing npm_config_userconfig=$HOME/.npmrc does not work (the nested pnpm recomputes and overrides it), and pinning a newer pnpm is a repo-wide package-manager move for a bug with no known fixed version on this line.

Impact

No release can be published until this lands. Nothing was partially published — the run died before the first registry write.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions