npm install errors: read the code, not the stack
Every npm ERR code names one cause. ERESOLVE, E404, EJSONPARSE and the rest, with what each one means, measured against real installs.
Every npm failure starts with a line that names the cause:
npm error code ERESOLVE
That code is the diagnosis. The forty lines underneath it are supporting detail, and reading them before the code is why npm errors feel impenetrable. Six codes account for almost everything you will hit.
| Code | What it means |
|---|---|
ERESOLVE |
Two packages want different versions of the same dependency |
E404 |
The package name does not exist in the registry |
EJSONPARSE |
Your package.json is not valid JSON |
EUSAGE |
The command was used wrong, commonly npm ci with no lockfile |
EACCES |
Permission denied, usually from a global install |
ENOENT |
A file or directory npm expected is not there |
Every output below came from real installs on npm 11.
ERESOLVE: a peer dependency conflict
The most common and the most misread. Here is a genuine one, produced by asking for React 17 alongside a library that requires 18 or 19:
npm error code ERESOLVE
npm error ERESOLVE unable to resolve dependency tree
npm error
npm error While resolving: conflict-demo@1.0.0
npm error Found: react@17.0.2
npm error node_modules/react
npm error react@"17.0.2" from the root project
npm error
npm error Could not resolve dependency:
npm error peer react@"^18.0.0 || ^19.0.0" from @testing-library/react@16.1.0
Two lines matter. Found: is what your project asks for. Could not resolve dependency: peer is what something else demands. You asked for React 17, and @testing-library/react requires 18 or 19. Those cannot both be true.
A peer dependency is a package saying "I work alongside this, but you install it, and it has to be a version I support". npm 7 and later enforce that. npm 6 did not, which is why old advice and old projects behave so differently.
The honest fix is to make the versions agree: upgrade React, or install a version of the library that supports React 17. npm info @testing-library/react peerDependencies tells you what a version wants before you commit to it.
What --force and --legacy-peer-deps actually do
npm suggests both flags in the error text and explains neither. Installing the same broken project with each produced measurably different trees:
| Flag | Packages installed | React version |
|---|---|---|
--legacy-peer-deps |
6 | 17.0.2 |
--force |
18 | 17.0.2 |
--legacy-peer-deps ignores peer dependencies entirely, behaving like npm 6. It installed six packages and never tried to satisfy the peer requirement at all.
--force installs anyway, pulling in the peer packages and warning about each one. It installed eighteen packages, including the React DOM that @testing-library/react asked for, while leaving React itself at the version the library rejects.
Both leave the same underlying mismatch: React 17 alongside a library that does not support it. Neither is a fix. --legacy-peer-deps is the safer of the two because it changes less, and it is a reasonable holding measure while you sort out the real version problem. Treat either flag as a note to come back, not as a resolution.
E404: the package does not exist
npm error code E404
npm error 404 Not Found - GET https://registry.npmjs.org/this-package-truly-does-not-exist-9f3a2b
Usually a typo. Occasionally a private package you are not authenticated for, or a scoped name missing its scope (@org/thing, not thing).
It is also what you get from a package name an AI assistant invented. That deserves more than a quick correction, because the name being unregistered is the good outcome. AI hallucinations in code covers what happens when somebody has already registered it.
EJSONPARSE: your package.json is malformed
npm error code EJSONPARSE
npm error JSON.parse Invalid package.json: Expected double-quoted property name in JSON at position 12
npm error JSON.parse Note: package.json must be actual JSON, not just JavaScript.
That last line is npm anticipating the mistake. package.json is JSON, not JavaScript, so trailing commas are illegal, comments are illegal, and every key needs double quotes. The position number counts characters into the file.
EUSAGE: npm ci without a lockfile
npm error code EUSAGE
npm error The `npm ci` command can only install with an existing package-lock.json
npm ci and npm install are different commands, and the difference matters more than the names suggest:
npm installreadspackage.json, resolves versions, installs, and writespackage-lock.json. It can change your lockfile.npm cireadspackage-lock.json, deletesnode_modules, and installs exactly what the lockfile says. It never changes the lockfile, and it fails if the lockfile andpackage.jsondisagree.
That is why npm ci belongs in CI and npm install belongs on your machine. A build running npm install can quietly get different versions than you tested with. A build running npm ci cannot.
EACCES: permission denied
Almost always a global install on macOS or Linux:
npm install -g some-tool
# npm error code EACCES
The advice you will find most often is to prefix it with sudo, and it is the wrong answer. Running an installer as root lets any package's install script run as root on your machine, and it leaves root-owned files in your npm cache that cause fresh permission errors later.
The right fix is to stop needing root at all. A version manager such as nvm or fnm puts Node and its global packages inside your home directory, where you already have permission. For a tool you need once, npx some-tool runs it without installing globally.
ENOENT: npm is looking at the wrong folder
npm error code ENOENT
npm error syscall open
npm error path /your/current/folder/package.json
npm error enoent Could not read package.json
ENOENT is "error, no entry", and the path line names exactly what npm could not find. When that path ends in package.json, you are running npm somewhere that is not a Node project.
Nearly always this is the same mistake as running git outside a repository: the terminal opened somewhere else, or you are one directory above the project. ls and cd fix it. It is worth checking the path line before anything else, because it tells you which folder npm thinks it is in, and that is usually the entire bug.
A different ENOENT comes from a script pointing at a file that does not exist:
Error: Cannot find module '/your/project/missing.js'
Here npm ran your script correctly and Node could not find the file. Check the path in scripts, and check the file was actually committed rather than sitting untracked on the machine where it works.
Before you delete node_modules
Deleting node_modules and the lockfile is the folk remedy for everything, and it does fix a genuinely corrupted tree. It also throws away the record of which versions worked, which is the thing you most want when something is broken. Try the cheaper steps first:
- Read the code. Half of these need no reinstall at all.
EJSONPARSEis a text edit. npm ci. Rebuildsnode_modulesfrom the lockfile exactly, without touching the lockfile.npm cache clean --force, if you suspect a corrupted download.- Then delete
node_modules, keeppackage-lock.json, and runnpm ci.
Deleting the lockfile is the last resort rather than the first, because it re-resolves every version and can hand you a different tree than the one that used to work.
Reading the dependency path
The block of lines under an ERESOLVE is a path, and once you know the shape it is quick to read:
npm error Found: react@17.0.2
npm error node_modules/react
npm error react@"17.0.2" from the root project
Each entry is three lines: the resolved version, where it sits in node_modules, and who asked for it. from the root project means your own package.json. Anything else names the package that wants it.
That last line is what tells you whether the conflict is yours to fix. A demand coming from the root project is a version you chose and can change. A demand coming from a dependency of a dependency means you are choosing between upgrading the package in the middle, waiting for its maintainer, or overriding the resolution yourself with an overrides block in package.json.
npm ls <package> prints the same relationship for an installed tree and is usually easier to read than the error.
Making these readable
Two habits help more than any flag.
Read the first five lines, then stop. The code and the two or three lines under it contain the diagnosis. The rest is the dependency path, which is useful only once you know what you are looking for.
Keep Node and npm versions consistent across a team. A large share of "works on my machine" installs comes from one person on npm 6, where peer conflicts were silently ignored, and another on npm 11, where they are hard errors. An engines field in package.json and a .nvmrc file make that mismatch visible instead of mysterious.
Want to work through installs and dependency trees somewhere they cannot break your machine? Start with the JavaScript track.
More from the blog
How to resolve merge conflicts in Git
A Git merge conflict happens when two branches change the same lines. How to read the markers, resolve each file, and the check that stops a bad commit.
Read moreGit vs GitHub: they are not the same thing
Git vs GitHub: Git is the version control tool on your computer, GitHub is a website that hosts Git repositories. What each one does and how they fit.
Read moreReady to write some code?
Put this into practice - start your first free lesson. No setup, no credit card.