You committed to pnpm two years ago. Your repo has pnpm-lock.yaml, your CI uses pnpm install --frozen-lockfile, your Dockerfile pins corepack enable && corepack prepare pnpm@9. Cursor or Claude Code does not care. It runs npm install lodash, creates a package-lock.json next to your existing pnpm-lock.yaml, possibly hoists differently than pnpm’s strict mode, and then commits both files. CI breaks because the workspace pnpm config no longer matches the npm-flat layout, a previously-resolved phantom dependency now fails to resolve, and your colleague pulls and gets a tangled node_modules. This is one of the most common low-grade AI failures and it compounds — once both lockfiles exist, every subsequent install is at risk.
Common causes
Ordered by how often each shows up.
1. AI defaults to npm because it is the most common in training data
The vast majority of public Node tutorials use npm install. The model has the npm command vocabulary deeply trained. Without explicit project-level signaling, it reaches for npm first.
How to spot it: AI suggests npm install <pkg> despite the repo having pnpm-lock.yaml or yarn.lock.
2. packageManager field missing from package.json
Modern Node tooling (corepack) uses "packageManager": "pnpm@9.x" in package.json to declare the canonical manager. Without it, AI has no machine-readable signal.
How to spot it: cat package.json | grep packageManager returns nothing.
3. AI ran npm to “fix” a problem it diagnosed wrong
It saw a missing dependency error, jumped to npm install <pkg>, did not consider that this would create a competing lockfile. Speed bias over correctness.
How to spot it: A package-lock.json shows up in a commit that was supposed to fix something else.
4. Mixed managers across team members or environments
One dev uses npm, another pnpm, CI uses yarn. AI inherits the inconsistency and chooses based on whatever it sees in the current open file’s directory.
How to spot it: Multiple lockfile types in version control history; teammates have different node_modules layouts.
5. AI generated npx commands that bypass the workspace’s manager
npx prisma generate works but does not respect pnpm’s workspace structure. In monorepos, this can resolve to a different prisma version than the workspace pins.
How to spot it: npx <tool> commands appear in AI-generated scripts; tool version differs between local and CI.
6. AI added “compatibility” scripts to package.json
Sometimes the AI invents "scripts": { "install:npm": "npm install" } for “convenience”. Now any teammate who runs that script poisons the lockfile.
How to spot it: Scripts in package.json reference both npm and pnpm/yarn commands.
Before you start
- Identify your canonical package manager. If unclear, ask the team — committing to one is half the fix.
- Make sure no in-progress changes from the AI are staged that include
package-lock.jsonor straynode_modulespaths. - Decide whether to enforce via
preinstallscript, corepack, CI check, or all three.
Information to collect
- Which lockfiles exist in the repo:
ls -la *.lock* *lock*.yaml *lock*.json. package.jsoncontent — specificallypackageManagerfield andengines..npmrc,.pnpmrc,.yarnrc.ymlif present.- CI config: which install command is used in pipelines.
- Dockerfile / devcontainer setup: what is installed on container start.
- Recent commits where the wrong lockfile was created or both exist.
Step-by-step fix
Ordered: clean up the current mess, then prevent recurrence.
Step 1: Delete the wrong lockfile and reinstall correctly
If your canonical manager is pnpm and a package-lock.json appeared:
rm -rf node_modules package-lock.json
pnpm install
git add package.json pnpm-lock.yaml
git rm package-lock.json 2>/dev/null
Repeat the analogous steps if your canonical is yarn or npm.
Step 2: Declare the canonical manager in package.json
{
"packageManager": "pnpm@9.7.0",
"engines": {
"node": ">=20",
"pnpm": ">=9"
}
}
With corepack enabled (corepack enable), running npm install in this repo will now warn or fail.
Step 3: Add a preinstall guard
In package.json:
{
"scripts": {
"preinstall": "npx only-allow pnpm"
}
}
only-allow is a tiny package that aborts install if the wrong manager is invoked. The error message is explicit: “Use pnpm, not npm.”
Step 4: Add a CI check that fails on duplicate lockfiles
In CI:
if [ -f "package-lock.json" ] || [ -f "yarn.lock" ]; then
echo "ERROR: Found a non-pnpm lockfile. This repo uses pnpm only."
exit 1
fi
This guarantees no PR with the wrong lockfile can merge.
Step 5: Tell the AI explicitly via rules file
In .cursorrules or CLAUDE.md:
This repo uses pnpm. Never use npm or yarn commands.
- Install: pnpm install
- Add package: pnpm add <pkg>
- Add dev dep: pnpm add -D <pkg>
- Remove: pnpm remove <pkg>
- Run script: pnpm run <script> or pnpm <script>
- Workspaces: pnpm --filter <pkg> <cmd>
Do NOT generate or run:
- npm install / npm ci / npm add
- yarn install / yarn add
- npx (use pnpm dlx instead if needed)
The preinstall hook will block npm/yarn anyway, but do not generate them.
This puts the policy directly in the AI’s working context.
Step 6: Use pnpm dlx / yarn dlx instead of npx
For one-off tool invocations:
# Instead of:
npx prisma generate
# Use:
pnpm dlx prisma generate
# or, better, pin it as a dev dep and run:
pnpm prisma generate
pnpm dlx respects the workspace’s resolver. Bonus: it tends to be faster than npx due to pnpm’s content-addressed store.
Step 7: Lock dev environment via corepack
In your README and Dockerfile:
corepack enable
corepack prepare pnpm@9.7.0 --activate
Now every dev environment and CI runner has the exact same pnpm version. AI cannot accidentally invoke a different version even if it tries pnpm correctly.
Verify
- Only one lockfile exists in the repo (
pnpm-lock.yamlonly, oryarn.lockonly, etc.). - Running
npm installin the repo fails fast with theonly-allowmessage. - CI fails on a PR that includes a foreign lockfile.
- AI prompted to “add a new package” generates
pnpm add(notnpm install) on the first try. - A fresh clone +
pnpm install --frozen-lockfileproduces a workingnode_modules.
Long-term prevention
- Always set
packageManagerinpackage.jsonfor any new repo. This is the single most effective guard. - Make
preinstallblock the wrong managers — it catches both AI and human mistakes. - Add CI checks for duplicate lockfiles AND for
package-lock.jsonsize growth (indicating accidental npm install). - Educate the AI via rules file once; do not rely on per-prompt corrections.
- For monorepos, prefer pnpm or yarn berry — npm workspaces are weakest and AI defaults will hurt more.
- Pin the manager version (not just the manager name) via corepack to avoid drift.
Common pitfalls
- Leaving
packageManagerunset and assuming “well, pnpm-lock.yaml is right there, the AI will figure it out”. It will not. - Deleting
package-lock.jsonbut forgetting to add the preinstall guard — the AI will recreate it next session. - Using
npxin AI-generated scripts for monorepo tools — picks up the wrong version silently. - Letting one teammate “just use npm because pnpm is annoying” — they generate the broken lockfile and the AI inherits the pattern from their commits.
- Committing both lockfiles “just in case” — they will diverge and one of them will be wrong.
- Not enabling corepack on CI, so the CI pnpm version differs from local. Symptoms look identical to the AI’s wrong-manager bug.
For related issues see AI package-lock conflicts, AI overwrote env vars, and AI build passes locally but fails in cloud.
FAQ
Q: Why does the AI default to npm even when pnpm-lock.yaml is visible?
Most public training data uses npm. The lockfile presence is a signal the AI may notice or ignore; the packageManager field is a much stronger signal. Set it.
Q: Can I just use both npm and pnpm if they share package.json?
No. They hoist differently, resolve peer deps differently, and produce incompatible node_modules. Pick one and enforce it.
Q: What if I want to migrate from npm to pnpm — how should I prompt the AI?
“This repo is migrating from npm to pnpm. Delete package-lock.json and node_modules. Run pnpm import to convert the lockfile. From now on, use pnpm exclusively. Add packageManager: pnpm@9 to package.json.”
Q: Will the preinstall guard break if a developer is offline?
only-allow is a tiny script that runs locally; it does not need network. It just inspects the npm_config_user_agent env var to decide. Works offline.
Tags: #Troubleshooting #AI coding #npm #pnpm #yarn #package-manager