Tools: Latest: How to Fix Dependency Rot in Projects You Haven't Touched in Months

Tools: Latest: How to Fix Dependency Rot in Projects You Haven't Touched in Months

Why This Happens

Step 1: Audit Before You Touch Anything

Step 2: Fix Your Runtime First

Step 3: The Nuclear Option (Done Right)

Step 4: Handle the Stubborn Failures

Prevention: Stop This From Happening Again

The Bigger Picture So the dev job market is warming back up — postings are up 15% since mid-2025 according to FRED data. If you're anything like me, that means you're dusting off side projects and portfolio repos that have been sitting untouched. And then you run npm install and everything explodes. Dependency rot is real, and it's one of the most frustrating problems in modern development. You didn't change a single line of code, but suddenly nothing builds. The root cause is deceptively simple: your lockfile pins exact versions, but the ecosystem around those versions keeps moving. Here's what actually goes wrong: The frustrating part is that the error messages rarely point to the actual cause. You'll see something like: That tells you what failed, but not why it was fine six months ago and broken now. Don't start deleting node_modules and reinstalling yet. First, understand the damage. I keep a small shell function in my .zshrc for this exact scenario: This gives you a snapshot before you start breaking things further. The single most common cause of "it worked before" is a runtime version mismatch. Fix this before touching dependencies. For Python projects, pyenv solves the same problem. I've lost count of how many times a project broke because my system Python jumped from 3.10 to 3.12 and some C extension wasn't compatible yet. Okay, now you can delete and reinstall. But do it properly. The difference matters. npm ci is strict — it either reproduces your lockfile exactly or fails. npm install will resolve fresh and update the lockfile. If npm ci fails but npm install works, your lockfile was the problem. For Python, the equivalent dance is: Sometimes a clean install still fails because a specific package genuinely doesn't work anymore. Here's my debugging flow: For native module failures specifically: Here's what I do now on every project, and it's saved me hours: With the job market picking back up, a lot of developers are revisiting old projects — whether for portfolio polish, interview prep, or just getting back into the rhythm. Don't let dependency rot be the thing that kills your momentum. Spend an afternoon getting your toolchain solid, set up the prevention guardrails, and you won't have to fight this battle again in six months. The irony of modern development is that doing nothing to a project is itself a form of technical debt. Code doesn't decay, but the ecosystem it depends on never stops moving. Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse

Command

Copy

# The classic unhelpful error -weight: 500;">npm ERR! code ERESOLVE -weight: 500;">npm ERR! ERESOLVE unable to resolve dependency tree -weight: 500;">npm ERR! Found: [email protected] -weight: 500;">npm ERR! node_modules/react -weight: 500;">npm ERR! react@"^18.2.0" from the root project -weight: 500;">npm ERR! peer react@">=18" from some-library@4.1.0 # The classic unhelpful error -weight: 500;">npm ERR! code ERESOLVE -weight: 500;">npm ERR! ERESOLVE unable to resolve dependency tree -weight: 500;">npm ERR! Found: [email protected] -weight: 500;">npm ERR! node_modules/react -weight: 500;">npm ERR! react@"^18.2.0" from the root project -weight: 500;">npm ERR! peer react@">=18" from some-library@4.1.0 # The classic unhelpful error -weight: 500;">npm ERR! code ERESOLVE -weight: 500;">npm ERR! ERESOLVE unable to resolve dependency tree -weight: 500;">npm ERR! Found: [email protected] -weight: 500;">npm ERR! node_modules/react -weight: 500;">npm ERR! react@"^18.2.0" from the root project -weight: 500;">npm ERR! peer react@">=18" from some-library@4.1.0 # Check what's actually outdated -weight: 500;">npm outdated # For a deeper look at vulnerabilities -weight: 500;">npm audit # If you're using Python, -weight: 500;">pip-audit is solid -weight: 500;">pip -weight: 500;">install -weight: 500;">pip-audit -weight: 500;">pip-audit # Check what's actually outdated -weight: 500;">npm outdated # For a deeper look at vulnerabilities -weight: 500;">npm audit # If you're using Python, -weight: 500;">pip-audit is solid -weight: 500;">pip -weight: 500;">install -weight: 500;">pip-audit -weight: 500;">pip-audit # Check what's actually outdated -weight: 500;">npm outdated # For a deeper look at vulnerabilities -weight: 500;">npm audit # If you're using Python, -weight: 500;">pip-audit is solid -weight: 500;">pip -weight: 500;">install -weight: 500;">pip-audit -weight: 500;">pip-audit # Quick health check for a stale project project-checkup() { echo "=== Runtime Versions ===" node -v 2>/dev/null && echo "Expected: $(cat .nvmrc 2>/dev/null || echo 'no .nvmrc')" python3 --version 2>/dev/null && echo "Expected: $(cat .python-version 2>/dev/null || echo 'no .python-version')" echo "\n=== Dependency Health ===" if [ -f package.json ]; then echo "Outdated packages:" -weight: 500;">npm outdated 2>/dev/null | head -20 echo "\nVulnerabilities:" -weight: 500;">npm audit 2>/dev/null | tail -5 fi if [ -f requirements.txt ]; then echo "Stale pins:" # Show packages that are more than one major version behind -weight: 500;">pip list --outdated 2>/dev/null | head -20 fi echo "\n=== Lockfile Status ===" -weight: 500;">git diff --stat HEAD -- '*.lock' 'package-lock.json' 2>/dev/null } # Quick health check for a stale project project-checkup() { echo "=== Runtime Versions ===" node -v 2>/dev/null && echo "Expected: $(cat .nvmrc 2>/dev/null || echo 'no .nvmrc')" python3 --version 2>/dev/null && echo "Expected: $(cat .python-version 2>/dev/null || echo 'no .python-version')" echo "\n=== Dependency Health ===" if [ -f package.json ]; then echo "Outdated packages:" -weight: 500;">npm outdated 2>/dev/null | head -20 echo "\nVulnerabilities:" -weight: 500;">npm audit 2>/dev/null | tail -5 fi if [ -f requirements.txt ]; then echo "Stale pins:" # Show packages that are more than one major version behind -weight: 500;">pip list --outdated 2>/dev/null | head -20 fi echo "\n=== Lockfile Status ===" -weight: 500;">git diff --stat HEAD -- '*.lock' 'package-lock.json' 2>/dev/null } # Quick health check for a stale project project-checkup() { echo "=== Runtime Versions ===" node -v 2>/dev/null && echo "Expected: $(cat .nvmrc 2>/dev/null || echo 'no .nvmrc')" python3 --version 2>/dev/null && echo "Expected: $(cat .python-version 2>/dev/null || echo 'no .python-version')" echo "\n=== Dependency Health ===" if [ -f package.json ]; then echo "Outdated packages:" -weight: 500;">npm outdated 2>/dev/null | head -20 echo "\nVulnerabilities:" -weight: 500;">npm audit 2>/dev/null | tail -5 fi if [ -f requirements.txt ]; then echo "Stale pins:" # Show packages that are more than one major version behind -weight: 500;">pip list --outdated 2>/dev/null | head -20 fi echo "\n=== Lockfile Status ===" -weight: 500;">git diff --stat HEAD -- '*.lock' 'package-lock.json' 2>/dev/null } # If you have .nvmrc or .node-version nvm -weight: 500;">install # installs the version specified in .nvmrc nvm use # If you don't have a version file, check your lockfile # The lockfile metadata often records what version generated it head -5 package-lock.json # Look for: "lockfileVersion": 2 means -weight: 500;">npm 7-8 (Node 16-18 era) # "lockfileVersion": 3 means -weight: 500;">npm 9+ (Node 18+ era) # If you have .nvmrc or .node-version nvm -weight: 500;">install # installs the version specified in .nvmrc nvm use # If you don't have a version file, check your lockfile # The lockfile metadata often records what version generated it head -5 package-lock.json # Look for: "lockfileVersion": 2 means -weight: 500;">npm 7-8 (Node 16-18 era) # "lockfileVersion": 3 means -weight: 500;">npm 9+ (Node 18+ era) # If you have .nvmrc or .node-version nvm -weight: 500;">install # installs the version specified in .nvmrc nvm use # If you don't have a version file, check your lockfile # The lockfile metadata often records what version generated it head -5 package-lock.json # Look for: "lockfileVersion": 2 means -weight: 500;">npm 7-8 (Node 16-18 era) # "lockfileVersion": 3 means -weight: 500;">npm 9+ (Node 18+ era) # JavaScript/Node projects rm -rf node_modules rm package-lock.json # yes, really — if it's deeply broken -weight: 500;">npm -weight: 500;">install # If you want to preserve your lock as much as possible instead: rm -rf node_modules -weight: 500;">npm ci # installs exactly what the lockfile says, fails if it can't # JavaScript/Node projects rm -rf node_modules rm package-lock.json # yes, really — if it's deeply broken -weight: 500;">npm -weight: 500;">install # If you want to preserve your lock as much as possible instead: rm -rf node_modules -weight: 500;">npm ci # installs exactly what the lockfile says, fails if it can't # JavaScript/Node projects rm -rf node_modules rm package-lock.json # yes, really — if it's deeply broken -weight: 500;">npm -weight: 500;">install # If you want to preserve your lock as much as possible instead: rm -rf node_modules -weight: 500;">npm ci # installs exactly what the lockfile says, fails if it can't # Recreate your virtual environment from scratch rm -rf .venv python -m venv .venv source .venv/bin/activate -weight: 500;">pip -weight: 500;">install -r requirements.txt # If using poetry: poetry env -weight: 500;">remove python poetry -weight: 500;">install # Recreate your virtual environment from scratch rm -rf .venv python -m venv .venv source .venv/bin/activate -weight: 500;">pip -weight: 500;">install -r requirements.txt # If using poetry: poetry env -weight: 500;">remove python poetry -weight: 500;">install # Recreate your virtual environment from scratch rm -rf .venv python -m venv .venv source .venv/bin/activate -weight: 500;">pip -weight: 500;">install -r requirements.txt # If using poetry: poetry env -weight: 500;">remove python poetry -weight: 500;">install # Rebuild native modules against your current runtime -weight: 500;">npm rebuild # If that fails, the nuclear option for native deps -weight: 500;">npm rebuild --build-from-source # For node-gyp issues specifically, make sure build tools are current # macOS: xcode-select ---weight: 500;">install # Ubuntu/Debian: -weight: 600;">sudo -weight: 500;">apt-get -weight: 500;">install build-essential # Rebuild native modules against your current runtime -weight: 500;">npm rebuild # If that fails, the nuclear option for native deps -weight: 500;">npm rebuild --build-from-source # For node-gyp issues specifically, make sure build tools are current # macOS: xcode-select ---weight: 500;">install # Ubuntu/Debian: -weight: 600;">sudo -weight: 500;">apt-get -weight: 500;">install build-essential # Rebuild native modules against your current runtime -weight: 500;">npm rebuild # If that fails, the nuclear option for native deps -weight: 500;">npm rebuild --build-from-source # For node-gyp issues specifically, make sure build tools are current # macOS: xcode-select ---weight: 500;">install # Ubuntu/Debian: -weight: 600;">sudo -weight: 500;">apt-get -weight: 500;">install build-essential { "engines": { "node": ">=20.0.0 <23.0.0", "-weight: 500;">npm": ">=10.0.0" } } { "engines": { "node": ">=20.0.0 <23.0.0", "-weight: 500;">npm": ">=10.0.0" } } { "engines": { "node": ">=20.0.0 <23.0.0", "-weight: 500;">npm": ">=10.0.0" } } - Transitive dependencies get yanked or deprecated. A package three levels deep in your tree publishes a breaking change or gets removed entirely. - Node/Python/Ruby runtime versions drift. Your project expected Node 18, but your machine now runs Node 22. Native bindings compiled against the old version won't load. - Registry metadata changes. Package registries occasionally restructure URLs, deprecate old endpoints, or enforce new authentication requirements. - OS-level libraries -weight: 500;">update. That OpenSSL or libsqlite -weight: 500;">upgrade you didn't notice? It just broke a native module. - Read the actual error — scroll up past the -weight: 500;">npm summary to the real build output - Check if the package is maintained — visit the repo, look at the last commit date - Search issues for your error — someone else hit this, guaranteed - Check if a fork exists — abandoned packages often get community forks - Pin your runtime version. Add .nvmrc, .python-version, or .tool-versions (if you use asdf). No excuses. - Set up Dependabot or Renovate. Even on side projects. Renovate is free and can auto-merge patch updates. You'll get small, digestible PRs instead of one massive -weight: 500;">upgrade shock. - Use engines in package.json. This tells -weight: 500;">npm to fail fast if someone (including future you) tries to -weight: 500;">install with the wrong Node version. - Run CI on a schedule. A weekly cron job on your CI pipeline that just runs -weight: 500;">npm ci && -weight: 500;">npm test will catch rot before it compounds. Most CI platforms support scheduled workflows at no extra cost.