npm run build is green on your laptop. You push, and Vercel / Netlify / GitHub Actions go red with Cannot find module 'some-lib', SyntaxError: Unexpected token, or error TS2307. This is the single most common “environment drift” bug in AI-assisted coding — Claude Code or Cursor installed a new dependency locally, used a Node feature your CI doesn’t support, or edited a file your global gitignore skipped. This article walks through the 6 most common causes and a 5-minute reproduction-and-fix flow.
Common causes
Ordered by hit rate, highest first.
1. Lockfile not committed (most common)
Agent ran npm install some-lib and committed package.json but not package-lock.json / pnpm-lock.yaml / yarn.lock. CI runs npm ci and fails with “lock file out of sync,” or runs npm install and gets a different version that breaks APIs.
npm error code EUSAGE
npm error `npm ci` can only install packages when your package.json and
npm error package-lock.json or npm-shrinkwrap.json are in sync.
npm error Missing: some-lib@2.3.0 from lock file
How to spot it: git log --name-only -1 on the latest commit. If package.json changed but the lockfile didn’t, this is it.
2. Local Node version differs from CI
You’re on Node 22, CI defaults to Node 18. Agent used Array.prototype.toSorted() (Node 20+) or top-level ESM await (Node 14.8+) — works locally, SyntaxError in CI.
SyntaxError: Unexpected token 'await'
at wrapSafe (node:internal/modules/cjs/loader:1378:18)
How to spot it: Run node -v locally and compare to the first line of the CI log. A major version gap is highly suspicious.
3. AI relied on a globally installed tool
Agent added tsx, esbuild, or vite to a build script assuming it’s globally available. You have it installed globally, CI doesn’t, CI fails with command not found: tsx.
How to spot it: Reproduce in a clean Docker: docker run --rm -v $(pwd):/app -w /app node:18 npm ci && npm run build. Fastest way to surface CI-only failures.
4. Filename casing mismatch
macOS and Windows are case-insensitive by default; Linux (CI) is strict. Agent wrote import Button from './button' on macOS but the file is actually Button.tsx. Local resolves, CI fails with Cannot find module './button'.
How to spot it: git ls-files | grep -i filename shows the actual on-disk casing. Compare to the import.
5. Env var exists locally, missing in CI
Agent added process.env.SOME_API_KEY. You have it in local .env, CI doesn’t. At build time it’s undefined; injected into client code, runtime crashes with undefined is not a function. Astro / Next.js read env at build time, so CI fails immediately.
How to spot it: Grep the recent diff for every process.env.XXX and cross-check Vercel / Netlify / GitHub Actions env panels.
6. AI left in a console.log or dev-only mock
Agent stuffed mock data or fixture paths into next.config.js or astro.config.mjs during debugging. The files exist locally, not on CI, build fails with ENOENT: no such file or directory.
How to spot it: git diff main -- '*.config.*' to see every config change.
Shortest path to fix
Ordered by ROI. Steps 1-2 fix 70% of cases.
Step 1: Reproduce locally with CI’s exact Node version and npm ci
Fastest first step:
# Read the first line of CI log for the Node version, e.g. 18.19.0
nvm install 18.19.0
nvm use 18.19.0
rm -rf node_modules
npm ci # ci, not install
npm run build
If it reproduces, you can iterate locally. If local still passes, the gap is env vars or CI image — jump to Step 4.
Step 2: Check the lockfile is in the most recent commit
git log --name-only -5 | grep -E '(package-lock|pnpm-lock|yarn.lock)'
If the last 5 commits touched package.json but never the lockfile, fix it now:
npm install # regenerate lockfile
git add package.json package-lock.json
git commit -m "chore: sync lockfile after AI dependency add"
Step 3: Pin Node version in package.json and CI
package.json:
{
"engines": {
"node": ">=18.19.0 <19.0.0",
"npm": ">=10.0.0"
}
}
GitHub Actions:
- uses: actions/setup-node@v4
with:
node-version: '18.19.0'
cache: 'npm'
Vercel: Project Settings → General → Node.js Version, pick the exact version. Netlify: netlify.toml → [build.environment] NODE_VERSION = "18.19.0".
Add a .nvmrc so local devs auto-switch:
echo "18.19.0" > .nvmrc
Step 4: Reconcile env vars
List every process.env.X in code and diff against the deploy platform:
grep -rEo 'process\.env\.[A-Z_]+' src/ | sort -u
Cross-check the Vercel / Netlify env panel. Add anything missing and redeploy. Note: client-visible vars need the right prefix — NEXT_PUBLIC_* (Next.js), PUBLIC_* (Astro), VITE_* (Vite). Wrong prefix = undefined at compile time.
Step 5: Reproduce in a clean Docker
The ultimate reproduction:
docker run --rm -it -v "$(pwd):/app" -w /app node:18.19.0 sh -c "
npm ci &&
npm run build
"
This simulates CI with fully clean node_modules and strict Node version. If this passes, the remaining difference is platform env vars or build cache.
Prevention
- Pin the same Node version in
package.jsonengines,.nvmrc, CI workflow, and deploy platform — all four places - Always use
npm ci(notnpm install) locally so lockfile drift fails loudly - Add to CLAUDE.md /
.cursorrules: “Installing a new dependency requires committing the lockfile in the same commit aspackage.json” - Pre-commit hook: if
package.jsonchanged, require the lockfile to have changed too - Enforce lowercase or case-exact import paths — enable ESLint
import/no-unresolvedwith case-sensitive resolver - Commit a
.env.exampleand have CI validate every required env var is set before building
Related
- AI code broke build
- AI package lock conflicts
- AI pre-commit review workflow
- Claude Code SEO audit
- AI dependency upgrade workflow
- AI Agent Loops Without Making Progress
- AI Generated TypeScript Errors
- AI Added a Route That Bypasses Auth Middleware
- AI Invented a Wrong API Signature That Does Not Exist
- AI-Generated SQL Locks a Hot Table for Minutes
- AI Keeps Using Deprecated Syntax Despite Lint Errors
- AI Uses npm Commands in a pnpm or Yarn Project
Tags: #AI coding #Debug #Troubleshooting