Fix Failed to replace env in config: NPM_TOKEN
Separate an npmrc parsing failure from registry authentication, with reproduced npm and Yarn Classic controls and a secret-safe CI fix.
TL;DR#
Failed to replace env in config: ${NPM_TOKEN} means a configuration value refers to an environment variable that the package-manager process cannot see. Supply the variable before starting the command, in that same shell or CI step. If the project does not need the authentication entry, remove that entry rather than deleting every npm setting. Keep the braced reference in a shared .npmrc; do not replace it with a real secret.
The exact exception was reproduced with npm 6.14.18 and Yarn Classic 1.22.22 on 4 November 2026. npm 10.9.9 did not throw on the same configuration read, so this is not a claim that every npm release behaves identically. The reproduction used an isolated Node v22.23.4 container and a dummy value for parsing only; it did not authenticate to a registry.
The original Stack Overflow question shows this error followed by a secondary npm loading failure:
Error: Failed to replace env in config: ${NPM_TOKEN}Treat the first line as the useful diagnosis. An application build has not yet failed; the package manager cannot finish reading its settings.
Why it happens#
An npmrc authentication entry can contain a reference rather than the credential itself:
//registry.npmjs.org/:_authToken=${NPM_TOKEN}The npmrc reference documents environment replacement using a dollar sign followed by a braced variable name. It also describes the project, user, global and built-in configuration files. The offending entry can therefore live outside the repository: fixing the project file does not remove a stale reference in the user's configuration.
The npm 6 configuration parser throws when a matched variable is undefined. Yarn Classic's environment replacement function implements the same check. This explains why a command that appears unrelated to authentication can fail: parsing the configuration happens before the command gets to its useful work.
The source question attempted a shell command beginning with set -x. In the reproduced shell, that turned on tracing and set positional parameters; it did not export the token. Bash's built-in help also lists tracing and environment export as separate operations. A variable visible inside an application, a child process or another terminal is not automatically available to the package-manager process being started now.
Fix#
1. Check the environment without printing the secret#
Run this in the same terminal, or the same CI step, that will run the installation:
node -e "if (!process.env.NPM_TOKEN) { console.error('NPM_TOKEN is missing or empty'); process.exit(1); } console.log('NPM_TOKEN is available');"The command deliberately reports presence rather than the value. A successful result says that Node can see a non-empty variable. It says nothing about whether the credential is valid, expired or authorised for a particular package. Do not troubleshoot by printing the token into a shared build log.
If this check fails, inspect how your shell or CI secret manager provides variables. Editing the application's environment file is a different operation. In the reproduction, adding a .env file while keeping the process environment unchanged left the npm error unchanged. Loading that file during application startup cannot help an installation that must complete before the application starts.
If your application script separately reports dotenv: command not found, use the NestJS dotenv command diagnosis. That page distinguishes the dotenv library from the command-line wrapper; installing a loader is not required merely to use an already exported variable.
2. Expose the variable to the command that needs it#
In an interactive Bash terminal, read the credential without echoing it and export it before rerunning the package manager:
read -r -s -p 'npm token: ' NPM_TOKEN
printf '\n'
export NPM_TOKENBash's built-in help describes the read options and export behaviour; check them locally with help read and help export. This snippet is for an interactive Bash session, not an unattended CI runner. Use the runner's secret mechanism there. The variable name must match the name inside the braced reference, including its spelling.
For GitHub Actions, place the environment mapping on the same step as the install. This is a step to insert after the existing checkout and Node setup in your workflow:
- name: Install dependencies
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npm ciStore the credential as the repository's intended secret first. npm's private-package CI guide describes making a secret available through an environment variable and retaining a literal reference in the project configuration. Do not put the actual token in this YAML or the checked-in npmrc file. Supplying it only to a later build or deployment step does not make it available to the earlier install.
For a pnpm workflow that gets past authentication and then reports a frozen-lockfile failure, continue with ERR_PNPM_OUTDATED_LOCKFILE in CI. That is a disagreement between the manifest and lockfile; changing a token cannot repair it.
3. Remove an authentication entry only when it is unused#
Some projects inherit an npmrc token line even though their dependencies are all public and no authenticated registry operation is intended. In that case, remove the unused authentication entry. Preserve any needed scope-to-registry mappings, certificate settings and other configuration. Deleting the entire file as a first response can remove information unrelated to this error.
For a project that does install private dependencies, removing the reference merely discards its authentication setup. Supply the credential instead. Keep the registry scope in the entry: npm's authentication configuration documentation explains how credentials are associated with a registry host or path. Do not convert a registry-scoped setting into an unrestricted token key.
Verify the fix#
First repeat the environment presence check from the failing execution context. Then ask the same package manager to read a non-secret configuration value:
npm config get registryFor a Yarn Classic invocation, use its corresponding command:
yarn config get registryThe local controls changed only whether the process received the variable. npm's failing configuration read exited with an error; supplying the variable made it return the public registry URL successfully. Yarn Classic likewise changed from the replacement exception to its configured registry. No private package was downloaded in those controls.
Finally rerun the original installation with the real credential available. A config read passing is a parsing check, not a registry login test. If the installation now fails with an authentication response, inspect the credential's access to the intended registry and package. That is a later failure than the exception this page fixes.
If the installation succeeds but the subsequent script reports that dependencies are missing, check the local package.json exists, but node_modules missing diagnosis. It explains the install-before-script requirement and the separate case where production settings omit development dependencies.
One email a month — the latest explained errors
What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.
Why removing the braces can hide the problem#
An answer on the source thread suggests replacing the braced form with bare $NPM_TOKEN. The reproduction confirmed that npm's old parser then stopped throwing during a config read. That is not evidence of successful authentication: the bare form no longer matches the replacement expression in the parser. Avoid treating disappearance of an exception as proof that the right credential reached the registry.
The version distinction matters for the same reason. npm's current replacement implementation keeps an undefined variable reference rather than throwing, matching the modern npm control. Upgrading an old toolchain can remove this particular parser exception, but it does not create the missing environment variable or grant access to a private package. Keep the intended authentication configuration, supply its input at process startup, and verify the operation that originally needed it.
Frequently Asked Questions
Still stuck? Paste your error.
Paste the error message or stack trace — we check it against every fix and tool on the site.
Continue Reading
Fix "No license field" when package.json has a license
Read the path in the warning, find the stray package.json above your project, and silence the line without inventing an open-source licence.
Fix ERR spawn EINVAL from npx install-peerdeps on Windows
Why a lint script that adds Airbnb ESLint config with Yarn dies on Windows with ERR spawn EINVAL, the one-word change that makes it run, and what the fixed release does to your version ranges.
Fix npm ERR! Unexpected token '.' with nvm-windows
How to confirm the broken libnpmfund file in under a minute, why only npm 8.3.1 and later trip on it, and the reinstall that actually repairs the Node version you already have.


