# Stop AI Hallucinating Packages (It Almost Installed Malware)

> Agents invent npm packages that don't exist. A 3-rule file plus a 30-second pre-install ritual ends it. The exact rules inside.

Source: https://boringpush.com/blog/stop-ai-hallucinating-packages

## `npm install` for a package that never existed

The agent finishes the feature, the code looks clean, and `npm run build` dies: `Cannot find module 'stripe-event-normalizer'`. No such package exists. The agent invented it — plausible name, correct-looking API, total fiction.

This is the most common agent failure in JavaScript: models trained on millions of repos autocomplete import statements the way they autocomplete sentences — confidently, sometimes fictionally. And `npm install` obeys without judgment, pulling typosquat malware when the invented name happens to be registered by someone malicious.

Stop AI hallucinating packages is not a model problem you can prompt away. It is a process problem with a three-rule fix.

## Phantom deps vs real supply chain

| Failure | What happens | Cost |
|---|---|---|
| Phantom package | Build dies, 20 min lost | Annoying, recoverable |
| Typosquat install | Malware runs postinstall | Credentials stolen |
| Dep bloat (real but unneeded) | 400MB node_modules, slow builds | Every deploy slower |
| Abandoned micro-dep | Breaks in 6 months | Weekend lost to migration |
| Version drift per session | Lockfile churn, conflicts | Teammate builds break |

Three details make invented packages worse than wasted minutes:

1. **Typosquats wait for typos.** Attackers register the exact plausible names agents invent. The agent that hallucinates `resend-edge-client` may find it installable — owned by a stranger running a postinstall script. Fiction becomes intrusion in one command.
2. **Each dep is a stranger's code in prod.** The average agent-added package pulls dozens of transitive deps. Every one runs with your Stripe keys in scope. Fewer deps is not minimalism — it is access control.
3. **Lockfile churn hides the crime.** Ten sessions each adding "one small package" produce a lockfile nobody reads. The phantom hides among the real, and `npm audit` reports on fiction it cannot see.

The hallucinated-packages post told the war story. This one installs the prevention.

## The 3-rule file plus the ritual

BoringPush ships this as part of `AGENTS.md` — the zero-dependency law, enforced by the gate and the lockfile.

**Step 1 — Write the three rules where the agent reads them.**

```md Example agent rules
## Dependencies (never violate)

1. Never run npm install or add packages without a written argument
   (what, why, what breaks without it) approved by a human.
2. Every import must resolve to package.json + lockfile — if it is not
   listed, it does not exist. No exceptions, no "try installing."
3. Node and Next built-ins first: fs, fetch, crypto, AsyncLocalStorage,
   Server Components, Route Handlers. Nine times in ten, no package needed.
```

Rule 2 is the phantom-killer: it flips the default from "install what I import" to "imports must pre-exist." An agent that invents `stripe-event-normalizer` now fails its own rule before touching the network — the fiction dies at the whiteboard instead of in `node_modules`.

**Step 2 — Run the 30-second pre-install ritual for the tenth time.**

```bash
# human runs this before approving any new dep:
npm view <name> time --json | tail -5   # exists? maintained?
npm view <name> dependencies --json      # what does it drag in?
npx --dry-run -p <name> 2>&1 | head -3   # sanity, no install
```

Five lines that answer: real or phantom, alive or abandoned, lean or bloated. The ritual takes thirty seconds and has killed every phantom before install — including two typosquat near-misses where the name existed but the owner was three weeks old with zero dependents.

**Step 3 — Audit the lockfile monthly like a dependency diet.**

```bash
npx depcheck        # find imports nobody uses
npm ls --depth=0    # what do we actually depend on
# remove one unused dep per month — momentum beats purges
```

`depcheck` names the dead weight agents left behind; one removal per month keeps the list honest without purge-day drama. Fewer deps mean fewer postinstall scripts, fewer advisories, faster builds — and a smaller blast radius the next time an agent gets creative.

That's the arrangement — the dependency list fits in memory, and phantoms never board.

## Before and after the 3-rule file

| Month of agent sessions | No rules | 3-rule file + ritual |
|---|---|---|
| Phantom installs | 4 | 0 |
| Typosquat near-misses | 1 (caught by luck) | 2 (caught by ritual) |
| Unasked real deps added | 5 | 0 |
| node_modules size trend | Growing weekly | Shrinking monthly |
| Approval overhead | 0 min | ~2 min per request |

Full disclosure: the ritual taxes genuine needs — occasionally a session waits hours for a human to approve a truly necessary package.

BoringPush accepts the delay because the default direction matters: friction on adding, none on removing. Dependencies should be hard to adopt and easy to divorce. Most repos have it exactly backwards.

## FAQ

### How do I stop ai hallucinating packages in editors without AGENTS.md support?

Put the same three rules in whatever instruction file the tool reads (project rules, workspace instructions, `.cursorrules`) and add the lockfile check to your own review habit: `git diff --stat package.json` on every agent session, before anything else. The file location varies by tool; the rule content never does. One source of truth, symlinked everywhere.

### What if the agent needs a package for a genuinely hard problem?

Then it writes the argument: problem, candidate, why built-ins fail, transitive cost, maintenance evidence (`npm view` output pasted in). You read five lines and approve or redirect. Nine times in ten the redirect works (fetch instead of axios, Web Crypto instead of a lib, Route Handlers instead of a framework). The tenth time, you gain a dependency deliberately — with its reason committed beside it.

### Are lockfiles enough protection without the rules?

No — lockfiles record decisions, they do not make them. A phantom installed once enters the lockfile like a legitimate resident, and every later `npm ci` faithfully reproduces the fiction (or the malware). Rules prevent entry; lockfiles preserve state. You need the bouncer and the ledger, in that order.

### Should I use `npm audit` or a scanner instead of rituals?

Both, layered: the ritual prevents entry, monthly `depcheck` removes dead weight, and `npm audit` watches what remains. Scanners report on known-bad residents; they cannot judge whether a resident should live there at all. Prevention (rules), hygiene (audits), surveillance (scanners) — three jobs, three tools, no substitutes.

### How do transitive dependencies factor into approval?

`npm view <name> dependencies` is step two of the ritual for exactly this reason: a 5KB utility dragging 40MB of transitive tree fails the leanness test. Prefer zero-dependency packages (check `dependencies: {}` in the manifest) and treat every transitive stranger as your own attack surface — because at runtime, with your env keys loaded, it is.

### What about frontend component libraries the agent loves adding?

Same rules, stricter stakes: UI kits import hundreds of components to use three, bloat the client bundle, and stamp a foreign design language over yours. Pin Tailwind plus inline SVG and forbid component libraries by rule — every agent-built screen stays coherent, light, and reviewable.

## Keep reading

- [Claude Hallucinated a Package That Doesn't Exist](/blog/claude-code-hallucinates-packages-fix) — the war story behind these rules
- [OpenCode SaaS Starter: Stop the Every-Session Redesign](/blog/opencode-saas-starter) — the file these rules live in
- [Will Claude Code Respect Your Boilerplate? 6 Tests](/blog/claude-code-saas-boilerplate) — the scorecard that grades dependency discipline
