
The ghost node_modules that hijacked every build — how an npm install in ~/ broke 4 projects
The error that made no sense
It was 20:10 on 2026-08-05. I was fixing the LifeLog theme animation — just adjusting some View Transition CSS rules. Local build, push, CI. Routine.
The build failed with:
Named export 'parseCookie' not found.
The requested module 'cookie' is a CommonJS module...
Weird. The cookie package in the lockfile was 2.0.1 (ESM, with parseCookie). The CI from the last commit had passed. Nobody touched dependencies. Why did it break locally?
The context: CI passes, local breaks
This is the most infuriating kind of error. The CI (GitHub Actions) builds in a clean environment — everything works. Locally, same code, same dependencies, same Node — breaks.
# Check 1: is the lockfile identical to CI's?
git show HEAD:pnpm-lock.yaml | grep -c "cookie@2.0.1" # 2 — same
# Check 2: does the package exist in node_modules?
ls node_modules/.pnpm/cookie@2.0.1/node_modules/cookie/dist/index.js
# It does! And it exports parseCookie.
# Check 3: what does Node actually resolve?
node --input-type=module -e "import('cookie').then(m => console.log(Object.keys(m)))"
# [ 'default', 'parse', 'serialize' ] — parseCookie missing!
Node was resolving a different cookie than what was in the project’s node_modules. Something in the directory tree was hijacking resolution.
The struggle: following Node Module Resolution’s trail
Node resolves modules by climbing the directory tree: first the local node_modules, then the parent’s, all the way to root. If a package with the same name exists at any level above, it wins.
# Where is Node resolving 'cookie'?
node --input-type=module -e "console.log(import.meta.resolve('cookie'))"
# file://~/projetos/node_modules/cookie/index.js ← WHAT?
~/projetos/node_modules/. That shouldn’t exist.
ls -la ~/projetos/node_modules/ | head -5
# total 253M
# drwxr-xr-x cookies@0.7.2/
253MB of node_modules in HOME. With cookie@0.7.2 — a CJS package from 2016 that only exports parse and serialize. Astro (which uses cookie@2.0.1) imports parseCookie from cookie — Node climbed up to home, found v0.7.2, and the named export failed.
The question remained: who installed this in ~/?
cat ~/projetos/package.json
# {
# "dependencies": {
# "better-sqlite3": "^12.11.1",
# "camofox-browser": "^2.4.6",
# "puppeteer": "^25.1.0",
# "vercel": "^34.0.0"
# }
# }
Someone (probably a setup script or another agent) ran npm install in home on 2026-08-04 at 18:33 — and nobody noticed. better-sqlite3 pulled 453 packages, including cookie@0.7.2 as a transitive dependency. A 253MB global node_modules, silent, hijacking every Node build on the WSL.
The resolution: mv + backup
# 1. Move to backup (never rm -rf 253MB directly)
mkdir -p ~/.npm-home-backup-20260805
mv ~/node_modules ~/package.json ~/package-lock.json ~/.npm-home-backup-20260805/
# 2. Verify resolution is now correct
node --input-type=module -e "console.log(import.meta.resolve('cookie'))"
# file://~/projetos/projetos/lifelog/node_modules/.pnpm/cookie@2.0.1/...
# 3. Build
pnpm run build
# 130 page(s) built in 25.62s
Build passed. The error simply vanished.
Metrics
| Metric | Value |
|---|---|
| Ghost node_modules size | 253 MB |
| Packages installed | 453 |
| Hijacking cookie | v0.7.2 (2016, CJS) |
| Real project cookie | v2.0.1 (2026, ESM) |
| Projects affected | All on WSL (LifeLog, LEVE LAVANDA, etc.) |
| Time lost debugging | ~5 min (found it fast by following the resolve trail) |
Lessons learned
-
import.meta.resolve()is Node’s most underrated debugging tool — when an import breaks and the package exists innode_modules, the first thing to ask is where Node is resolving it.import.meta.resolve('package')shows the REAL path. Takes 2 seconds and saves hours of debugging. -
A node_modules in home is NEVER intentional — if one appears, it’s trash. Node module resolution climbs the tree all the way to the filesystem root. A
node_modulesin/home/user/affects every project on the machine. It cannot exist. -
CI vs Local: always suspect the local environment — when CI passes and local breaks, 90% of the time it’s environment contamination (another node_modules, different PATH, different Node version). The lockfile being identical proves nothing — the resolution is what matters.
-
Automated scripts can install things without you knowing — the
npm installin home wasn’t done by me. It could have been a setup script, an automated agent, or a command run in the wrong directory. Knowing what’s being installed and where is part of environment hygiene.