# Debugging Hell: How Verdaccio Configuration Broke My Vercel Deployment
Table of Contents
Prologue
Last Friday at 5 PM, I confidently typed vercel --prod into my terminal, ready to show the client our new features. The Vercel build immediately exploded with a cryptic error:
Error: Command "npm install" exited with 1That’s it? That’s all you’re giving me?
The Crime Scene: A Mysterious Build Failure
Here’s what the full error log looked like:
2025-10-04T08:55:11.987Z Running build in Washington, D.C., USA (East) – iad12025-10-04T08:55:17.848Z Installing dependencies...2025-10-04T08:56:38.421Z npm error Exit handler never called!2025-10-04T08:56:38.421Z npm error This is an error with npm itself. Please report this error at:2025-10-04T08:56:38.421Z npm error <https://github.com/npm/cli/issues>2025-10-04T08:56:38.451Z Error: Command "npm install" exited with 1“An error with npm itself”? I haven’t done anything unusual… or have I?
First Instinct: Standard Troubleshooting
# 1. Clear Vercel build cache$ vercel --prod --force# Failed - Same error
# 2. Test locally$ rm -rf node_modules$ npm install# Success - Works perfectly on my machine
# 3. Check Node versionAdded to package.json:
{ "engines": { "node": "18.x", "npm": "9.x" }}$ git add package.json$ git commit -m "fix: specify node version"$ git push$ vercel --prod# Failed - Still failingIt’s 6 PM now. The client is asking for updates on WeChat.
Down the Rabbit Hole: Trial and Error
Attempt 1: Suspect the Lock File
After browsing StackOverflow, many people suggested deleting the lock file:
$ rm package-lock.json$ npm install$ git add package-lock.json$ git commit -m "regenerate lock file"$ git push$ vercel --prod# Failed - Same error!Attempt 2: Modify Install Command
Tried using npm ci in vercel.json:
{ "installCommand": "npm ci --legacy-peer-deps"}$ vercel --prod# Failed - Failed with the same errorAttempt 3: Suspect a Problematic Dependency
Seeing “Exit handler never called” in the error, I searched and found it might be a postinstall script hanging:
# Check packages with postinstall scripts$ npm ls --all | grep -i postinstallFound several packages like sharp, puppeteer with install scripts.
Tried skipping scripts in vercel.json:
{ "installCommand": "npm install --ignore-scripts"}$ vercel --prod# Failed - Install succeeded but build failed (missing sharp binaries)It’s 7 PM now. I’m starting to question my career choices.
The Turning Point: Comparing with a Working Project
Suddenly remembered that another project deployed successfully last week, also using sharp.
Pulled it down to compare:
# Compare package.json$ diff project-a/package.json project-b/package.json# Dependencies are mostly the same
# Compare vercel.json$ diff project-a/vercel.json project-b/vercel.json# No significant differences
# Check .npmrc$ cat .npmrc# This project doesn't have one
$ cat ~/.npmrcregistry=http://192.168.3.20:4873/Wait, when did I change the global registry?
Flashback
This 192.168.3.20:4873 is a Verdaccio private npm registry I set up three months ago to speed up npm installs:
# Three months ago...$ npm install -g verdaccio$ verdaccio# Verdaccio running on http://localhost:4873/
# Then I configured the global registry$ npm config set registry http://192.168.3.20:4873/Install times went from 2 minutes to 20 seconds. The team loved it. I even shared this “optimization” in our weekly meeting.
But what does this have to do with Vercel deployment?
Bold Hypothesis
If package-lock.json contains private registry URLs ↓Vercel servers in the US can't access my company's internal network at 192.168.3.20 ↓npm install times out and fails?Quick verification:
$ grep "192.168.3.20" package-lock.json# ...tons of output... "resolved": "http://192.168.3.20:4873/sharp/-/sharp-0.34.4.tgz", "resolved": "http://192.168.3.20:4873/@img/colour/-/colour-1.0.0.tgz", "resolved": "http://192.168.3.20:4873/@shikijs/core/-/core-3.13.0.tgz",# ...200+ lines...Holy sh*t! Everything points to internal IPs!
But this is just a guess. I still need to confirm if Vercel is actually trying to request these addresses.
The Smoking Gun: Enabling Debug Logs
To verify my hypothesis, I need to see what npm is actually doing during Vercel deployment.
Modify vercel.json
{ "installCommand": "npm -d install"}The -d flag outputs npm debug information, including network requests.
git add vercel.jsongit commit -m "debug: add npm debug flag"git pushvercel --prodThis time the deployment logs finally revealed the key information:
19:56:22.766 Running "install" command: `npm -d install`...19:56:23.045 npm info using [email protected]19:56:23.046 npm info using [email protected]19:56:24.094 npm http cache sharp@http://192.168.3.20:4873/sharp/-/sharp-0.34.4.tgz 1ms (cache hit)19:56:24.096 npm http cache @img/colour@http://192.168.3.20:4873/@img/colour/-/colour-1.0.0.tgz 0ms (cache hit)19:56:24.097 npm http cache @shikijs/langs@http://192.168.3.20:4873/@shikijs/langs/-/langs-3.13.0.tgz 0ms (cache hit)...19:56:24.098 npm http fetch GET 200 http://192.168.3.20:4873/sharp/-/sharp-0.34.4.tgz (attempt 1)...19:56:38.234 npm http fetch GET timeout http://192.168.3.20:4873/@shikijs/core/-/core-3.13.0.tgz19:56:38.421 npm error Exit handler never called!Case closed!
npm reads the "resolved" field in package-lock.json ↓Tries to download packages from http://192.168.3.20:4873/ ↓This is an internal IP, unreachable from Vercel servers ↓Request times out (default 30 seconds) ↓npm internal state corrupts, "Exit handler never called" ↓Build failsWhy Does It Work Locally?
Because my computer is on the company network and CAN access 192.168.3.20!
A classic “works on my machine” moment.
Root Cause: npm’s Dependency Resolution Mechanism
The Secret of package-lock.json
I used to think the lock file only recorded version numbers. Actually, it records the complete download URLs:
{ "packages": { "node_modules/sharp": { "version": "0.34.4", "resolved": "http://192.168.3.20:4873/sharp/-/sharp-0.34.4.tgz", "integrity": "sha512-xxx..." } }}npm install Execution Logic
When npm install runs:1. Reads package-lock.json2. Downloads packages directly using the "resolved" field's complete URL3. Verifies using the "integrity" field's hash4. Only queries the registry if lock file is missing or corrupted
Key point: resolved field priority > registry configuration!The Contamination Chain
3 months ago: npm config set registry http://192.168.3.20:4873/ ↓At some point ran npm install (maybe when adding new dependencies) ↓npm resolved all packages from private registry, wrote to package-lock.json's resolved fields ↓Committed package-lock.json to Git ↓Vercel clones code, reads lock file ↓Tries to download packages from internal IP → timeout → failureThe Solution
1. Switch Back to Public Registry
# Check current config$ npm config get registryhttp://192.168.3.20:4873/ # Here's the problem
# Switch to public registry$ npm config set registry https://registry.npmjs.org/
# Verify$ npm config get registryhttps://registry.npmjs.org/ # Success2. Clean and Regenerate package-lock.json
# Delete the old one$ rm -rf node_modules package-lock.json
# Reinstall (now using public registry)$ npm install
# Verify it's clean$ grep "192.168.3.20" package-lock.json# (no output) - Clean!
# Check new resolved fields$ grep '"resolved"' package-lock.json | head -5 "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.4.tgz", "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.13.0.tgz",# All pointing to public registry now3. Remove Debug Flag and Deploy
// vercel.json - remove -d flag{ "buildCommand": "npm run build"}git add package-lock.json vercel.jsongit commit -m "fix: clean package-lock from private registry"git pushvercel --prodSuccess
19:56:17.848 Installing dependencies...19:56:23.156 npm http fetch GET 200 https://registry.npmjs.org/sharp 234ms19:56:24.234 npm http fetch GET 200 https://registry.npmjs.org/@img/colour 89ms...19:57:42.123 Build Completed19:57:43.456 Deployment ready8:30 PM. Finally deployed successfully. I told the client there was a “temporary Vercel server issue.” They bought it.
Retrospective: If I Could Do It Over
The debugging process should have been
1. Build fails with unclear error ↓2. Check if reproducible locally ↓ Works locally → environment difference3. Compare local vs CI environment: - Node/npm versions (checked) - Environment variables (checked) - npm configuration ← Should check here! - Network access ← And here! ↓4. Add detailed logs to confirm hypothesis ↓5. Identify root cause and fixMy detours
- Blindly trying various configs (Node version, install params, etc.)
- Prematurely suspecting specific dependencies (sharp, puppeteer)
- Didn’t check npm config first thing
- Eventually used detailed logs to pinpoint issue (but should’ve done this earlier)
If I Had Added -d Flag from the Start
// Should have done this after first deployment failure{ "installCommand": "npm -d install"}Would have saved at least 2 hours of debugging!
Prevention Measures
To avoid stepping on this rake again, I implemented these improvements:
1. Never Configure Private Registry Globally
# Dangerous operation - DON'T DO THISnpm config set registry http://192.168.3.20:4873/
# Correct approach: use scoped configuration# .npmrc (commit to Git)registry=https://registry.npmjs.org/@mycompany:registry=http://192.168.3.20:4873/
# Only @mycompany/* packages use private registry# Everything else uses public registry2. Add Git Pre-commit Hook
#!/bin/shif git diff --cached --name-only | grep -q package-lock.json; then # Check for internal IPs if grep -qE '192\.168\.|10\.|172\.(1[6-9]|2[0-9]|3[0-1])\.|localhost|127\.0\.0\.1' package-lock.json; then echo "ERROR: Private registry detected in package-lock.json" echo "" echo "Found internal IP addresses:" grep -oE '192\.168\.[0-9]+\.[0-9]+|10\.[0-9]+\.[0-9]+\.[0-9]+' package-lock.json | sort -u echo "" echo "Fix with:" echo " npm config set registry https://registry.npmjs.org/" echo " rm -rf node_modules package-lock.json" echo " npm install" exit 1 fifichmod +x .husky/pre-commit3. Force Public Registry in CI Config
{ "buildCommand": "npm run build", "installCommand": "npm config set registry https://registry.npmjs.org && npm ci"}4. Team Documentation
Wrote an “npm Private Registry Usage Guidelines” with key points:
- Only configure private registry in
.npmrc(not globally) - Use scoped registries for private packages (
@mycompany:registry=...) - Check
package-lock.jsonbefore committing - Regularly test builds in clean environments
Technical Insights
1. npm Configuration Priority
npm package lookup order:1. package-lock.json "resolved" field (highest priority!)2. Project .npmrc3. User ~/.npmrc4. Global npm config5. npm built-in defaultsI had no idea the resolved field had such high priority!
2. package-lock.json Is More Than Version Locking
It also contains:
- Complete download URLs (
resolved) - File integrity checksums (
integrity) - Dependency tree structure (
dependencies)
This is why:
- Commit lock file = fully reproducible builds
- Don’t commit lock file = “works on my machine”
3. Debugging Techniques
# npm debug logsnpm install -d # Detailed debug infonpm install --verbose # Verbose outputnpm install --timing # Performance analysis
# See actual requestsnpm install --loglevel=silly 2>&1 | grep "http fetch"
# View configurationnpm config list --json4. Vercel Debugging Tricks
// vercel.json can override install command{ "installCommand": "npm -d install", // For debugging "buildCommand": "npm run build"}You can also set environment variables in Vercel Dashboard:
NPM_CONFIG_LOGLEVEL=sillyNPM_CONFIG_REGISTRY=https://registry.npmjs.org/Final Thoughts
This incident took 3 hours to debug, but gave me a much deeper understanding of the npm ecosystem.
Key Takeaway
When facing “works locally, fails in CI” issues, first thing to do:
- Add detailed logging (
npm -d install) - Check environment differences (config, network, permissions)
- Check
package-lock.jsonresolvedfields
Not:
- Blindly trying various configurations
- Deleting lock file (might mask the problem)
- Suspecting specific dependencies
Reader’s Checklist
If your Vercel/CI deployment also has mysterious npm install failures, try this:
# 1. Check npm configurationnpm config get registry
# 2. Check package-lock.jsongrep -E '192\.168\.|localhost|127\.0\.0\.1|10\.' package-lock.json
# 3. If private registry URLs found, clean itnpm config set registry https://registry.npmjs.org/rm -rf node_modules package-lock.jsonnpm install
# 4. Redeploygit add package-lock.jsongit commit -m "fix: clean lock file"git pushHope this article helps anyone facing similar issues!
Related Resources:
- npm package-lock.json Official Docs
- Vercel Deployment Troubleshooting Guide
- Verdaccio Official Documentation
Had similar experiences? Share your debugging stories in the comments!