Every repo in the org commits a .npmrc whose GitHub Packages token is an environment variable:
@ferrlabs:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}
always-auth=true
pnpm 11 and later refuse to expand that variable, on the grounds that a committed file could leak the secret to an attacker-controlled registry:
[WARN] Ignored project-level auth setting "//npm.pkg.github.com/:_authToken" in
"/home/runner/_work/FerrLens-Cloud/FerrLens-Cloud/.npmrc": environment variables are not
expanded in registry credentials that come from a project .npmrc, because that file is
committed to the repository and could leak the secret to an attacker-controlled registry.
Found while getting FerrLens-Cloud onto pnpm 12 (FerrLabs/FerrLens-Cloud#451).
CI is not affected, and it is worth being precise about why
reusable-ci-node.yml passes registry-url and scope to actions/setup-node, which writes a user-level npmrc and points pnpm at it:
NPM_CONFIG_USERCONFIG: /home/runner/_work/_temp/.npmrc
pnpm still expands variables from a user-level config, so the token arrives through that path and resolution completes. The committed project .npmrc is redundant in CI and contributes only two warning lines per install.
So this is not a CI outage, and nothing is on fire.
What does break: local development
A developer who exports NODE_AUTH_TOKEN and relies on the committed .npmrc has no user-level config to fall back on. Under pnpm 11+ their token is silently dropped, and the first cold-store fetch of a private @ferrlabs/* package 401s. The failure is confusing because the warning is easy to scroll past and the .npmrc looks correct.
Today the blast radius is small because most people have a warm pnpm store. It grows the moment someone clones fresh, clears the store, or onboards.
Scope
All nine repos carry the identical committed file:
FerrLens-Cloud FerrFlow-Cloud FerrVault-Cloud
FerrTrack-Cloud FerrGrowth-Cloud FerrAgents
FerrLabs-Cloud FerrGames-Cloud UI
The trigger is pnpm 11+, so it lands per repo as each one takes the bump. FerrLens is first (#451); the rest follow.
Options
- Keep the registry mapping in the project file, move the credential out.
@ferrlabs:registry=... is not a secret and belongs in the repo; only the _authToken line is the problem. Document pnpm config set "//npm.pkg.github.com/:_authToken" <token> as the one-time developer setup, and drop the line from all nine .npmrc files. This is what pnpm's own message recommends and it removes the warning from CI too.
- Leave it and accept the warnings. Defensible only until someone hits the cold-store 401; the diagnosis cost is the real price.
Option 1 also removes a small footgun that predates pnpm 11: a committed file that looks like it carries a credential invites someone to paste a literal token into it one day.
Suggested work
- Add the
pnpm config set step to the contributor setup docs.
- Strip the
_authToken line from the nine .npmrc files, keeping the registry mapping.
- Confirm
reusable-ci-node.yml needs no change (it does not — setup-node already supplies the credential), and note that in the workflow so the next reader does not re-add it.
Every repo in the org commits a
.npmrcwhose GitHub Packages token is an environment variable:pnpm 11 and later refuse to expand that variable, on the grounds that a committed file could leak the secret to an attacker-controlled registry:
Found while getting FerrLens-Cloud onto pnpm 12 (FerrLabs/FerrLens-Cloud#451).
CI is not affected, and it is worth being precise about why
reusable-ci-node.ymlpassesregistry-urlandscopetoactions/setup-node, which writes a user-level npmrc and points pnpm at it:pnpm still expands variables from a user-level config, so the token arrives through that path and resolution completes. The committed project
.npmrcis redundant in CI and contributes only two warning lines per install.So this is not a CI outage, and nothing is on fire.
What does break: local development
A developer who exports
NODE_AUTH_TOKENand relies on the committed.npmrchas no user-level config to fall back on. Under pnpm 11+ their token is silently dropped, and the first cold-store fetch of a private@ferrlabs/*package 401s. The failure is confusing because the warning is easy to scroll past and the.npmrclooks correct.Today the blast radius is small because most people have a warm pnpm store. It grows the moment someone clones fresh, clears the store, or onboards.
Scope
All nine repos carry the identical committed file:
The trigger is pnpm 11+, so it lands per repo as each one takes the bump. FerrLens is first (#451); the rest follow.
Options
@ferrlabs:registry=...is not a secret and belongs in the repo; only the_authTokenline is the problem. Documentpnpm config set "//npm.pkg.github.com/:_authToken" <token>as the one-time developer setup, and drop the line from all nine.npmrcfiles. This is what pnpm's own message recommends and it removes the warning from CI too.Option 1 also removes a small footgun that predates pnpm 11: a committed file that looks like it carries a credential invites someone to paste a literal token into it one day.
Suggested work
pnpm config setstep to the contributor setup docs._authTokenline from the nine.npmrcfiles, keeping the registry mapping.reusable-ci-node.ymlneeds no change (it does not — setup-node already supplies the credential), and note that in the workflow so the next reader does not re-add it.