A dependency update broke your app, but deleting node_modules and trying the same install again is not a diagnosis. First restore the exact dependency state that worked. Then compare the manifest and lockfile, identify the package that moved, and test one deliberate update at a time.
If the break followed npm audit fix --force, treat the command as the likely change event, not proof that every reported vulnerability was exploitable. npm documents that audit fix runs a full install and that --force may install SemVer-major versions outside your declared dependency range. That can be a valid security fix, but it can also introduce a breaking API, peer-dependency conflict, runtime change, or build-tool change.
Recover a working build first
Your quickest safe recovery target is the last known-good package.json and lockfile together. Restoring only package-lock.json may not be enough if the command also changed a version range in package.json.
- 01 Save the failed build log and the manifest and lockfile diff before changing anything else.
- 02 Restore package.json, the lockfile, and project-level package-manager configuration from the same known-good commit.
- 03 Run the package manager's clean, lockfile-based install, such as npm ci for an npm project.
- 04 Run the same type check, tests, build, and critical-flow checks that production uses.
- 05 Keep the known-good deployment live while you investigate the update separately.
For an npm project, the verification sequence often starts here:
npm ci
npm test
npm run build
Use your repository’s actual commands. A green install proves only that npm could construct the tree. It does not prove that the application compiles, starts, or still completes its important flows.
npm’s current npm ci documentation adds an important reproducibility condition. If the lockfile was created with a tree-shaping option such as --legacy-peer-deps or --install-links, the clean install needs the same option. Restore the committed project .npmrc and the repository’s pinned Node and npm versions with the manifest and lockfile. Do not add a new compatibility flag merely to silence an error from a lockfile created under different settings.
If the last known-good files are already committed, restore them through your normal version-control workflow or open a clean worktree at that commit. Avoid hand-editing transitive versions in the lockfile. The lockfile is generated state, and a manual change can create a tree that neither your manifest nor npm can reproduce.
Find which dependency change caused the regression
Start with the diff. In package.json, look for new ranges or changed direct dependencies. In the lockfile, identify which direct package pulled the changed transitive subtree. Then match the failure to the type of change.
| Failure after the update | First place to inspect |
|---|---|
| TypeScript or build error | Major-version API changes, removed exports, changed types, or stricter compiler and plugin versions |
| Install or peer-dependency error | The peer ranges required by the package that moved and the versions actually resolved |
| App starts but a route fails | Runtime changelog, changed defaults, environment requirements, and the stack trace for that route |
| Only CI or production fails | Node version, operating system, native modules, build cache, and environment differences |
Do not update the whole tree again to see whether the problem disappears. Reproduce the regression from the known-good state by changing one direct dependency to one explicit target version. For example:
npm audit fix --dry-run --json
npm outdated
npm install <package>@<target-version>
npm test
npm run build
The current npm audit documentation describes the dry run and explains the install behavior behind audit fix. The dry run is a prediction, not a guarantee. Read the affected package’s release notes and migration guide before accepting a major update.
If several direct packages changed, test them in separate branches or commits. When the failure appears after one change, you have a small, reviewable regression rather than a lockfile containing hundreds of unexplained differences.
npm audit fix is not a reachability decision
An advisory scanner determines that a vulnerable package version is present in the dependency tree. That is useful evidence, but presence alone does not establish that the application can reach the vulnerable function, route, parser, or configuration.
The fixed AxonBuild research cohort illustrates both sides. In 3 of 21 third-party apps, scanners reported between 33 and 44 dependency vulnerabilities while code-path review found zero that the app could trigger. In a separate fixed-cohort result, 8 of 21 third-party apps plus one founder app did run a framework version with a publicly known, reachable remote-code-execution or authentication-bypass issue.
Those figures are historical observations about a selected cohort, not a claim that most audit warnings are harmless. They show why the correct unit of work is one advisory at a time:
- Confirm the installed version and the advisory’s affected range.
- Identify the vulnerable behavior and its preconditions.
- Trace whether the running application reaches that behavior.
- Apply the smallest supported fixed version.
- Test the paths that use the package before deployment.
One audited AI CMS had a Next.js version with a documented middleware-bypass issue on the same middleware path used as its login gate. Package presence was only the starting evidence. The affected behavior sat on the request path protecting authenticated pages, so the update was urgent and the relevant fix stayed within the same major release line.
The dependency update that broke your app was usually fixing a vulnerability nothing could reach. The one worth running is a single line, and only reachability tells them apart.
Should you run npm audit fix --force again?
Not as a blanket retry. The --force option allows npm to propose changes outside normal dependency constraints, including SemVer-major updates to top-level packages. It does not mean every run will install a major version, and it does not establish that the resulting application is compatible.
Use the report to build a review queue instead:
- Patch a reachable, high-impact issue promptly, using the smallest supported fixed release.
- Test a required major upgrade as a normal application change, with its migration guide and regression checks.
- Document an advisory that is not reachable, including the version, path analysis, and what change would make it reachable.
- Revisit the decision when application code, package versions, or advisory details change.
Plain npm audit fix is not risk-free either. npm says it runs a full install, and even a SemVer-compatible release can contain a regression, changed transitive resolution, install script, or behavior your tests do not cover. Preview the result, review the diff, and run production-equivalent checks.
Prevent the next dependency update from breaking production
The durable fix is a controlled dependency lane, not permanent version stasis. Keep a committed lockfile, use deterministic installs in CI, apply small updates, and require the build plus critical-flow tests before production receives them.
That gate matters because one database with no staging is a deploy set to break when every dependency change reaches the only environment immediately. The neglect is measurable: across the AxonBuild audit corpus, Dependencies & Supply Chain averaged 34.5 out of 100, the second-worst of the twelve pillars scored, because nobody moves a version while nothing is crashing, until the day something forces the question. Dependency drift is also part of why an AI-built app gets harder to change: a long-delayed upgrade combines many migration steps into one risky event. The broader readiness owner, is your AI-built app ready to launch, covers the controls around this update lane.
At minimum, make every dependency change answer four questions in the pull request or commit:
- What direct and transitive versions changed?
- Which advisory, defect, or compatibility need justified the change?
- Which application paths use the changed package?
- Which checks passed against those paths?
Common questions about dependency-update failures
How do I undo an npm update that broke my app?
Restore package.json and package-lock.json from the same known-good commit, run npm ci, and execute the repository’s tests and build. Preserve the failed diff first so you can identify the changed package afterward.
Is deleting node_modules enough?
No. Reinstalling from the newly changed lockfile usually recreates the same dependency tree. Deleting node_modules helps only when the installed directory is inconsistent with a correct manifest and lockfile.
Is npm audit fix safe without --force?
It stays within npm’s normal compatibility constraints, but it still runs an install and can change the resolved tree. Preview the change, review the manifest and lockfile diff, and test the application before deploying it.
How do I know whether a CVE affects my app?
Confirm the installed version, read the advisory’s vulnerable behavior and preconditions, then trace whether your code or configuration reaches that behavior. Package presence is the start of the investigation, not its conclusion.
When every fix and release still depends on you
AxonBuild can trace the failure, repair the broken workflow, and ship the next change without rebuilding the parts that already work.