"pnpm install just stopped with ERR_PNPM_NO_MATCHING_VERSION." If you have ever shipped a refactor through Antigravity's agents and run face-first into that message, you are far from alone. The first time it happened to me, the import in question was react-hook-toast — a name so plausible that I doubted my own memory before I doubted the model.
The community has started calling this behavior "phantom imports" or, less charitably, "slopsquatting bait." It isn't unique to Antigravity — Cursor and Claude Code see it too — but Antigravity's manager surface tends to make the diagnosis harder, because several agents may have touched the file before you got a chance to read the diff. Left unchecked, phantom imports cost you build time and quietly widen your supply-chain attack surface. Let's walk through why this happens and how to lock it down without slowing your workflow.
Why a model invents "almost real" package names
Phantom imports are a side effect of how language models optimize for plausibility. react-hook-toast does not exist, but react-hot-toast, react-hook-form, and use-toast all do, and their tokens co-occur in training data often enough that the model treats the invented name as just as natural as the real ones. The same mechanism produces next-auth-helpers (a blend of @supabase/auth-helpers-nextjs and next-auth) and tailwind-merge-cn (tailwind-merge plus the cn utility from shadcn).
It gets worse inside Antigravity. When the manager surface dispatches a "code edit" task, it usually does not feed the entire package.json back to the worker — token budgets force a summary. If you ask for "add toast notifications" against a sparse context, the agent picks the most familiar-looking name from a fuzzy memory rather than from your actual lockfile.
In my own projects, three situations reproduce the bug almost on demand:
- A single prompt that bundles several features (auth + notifications + analytics)
- A bump-many-versions task where the agent edits imports as a side effect
- A fresh workspace where only the README is in the agent's context
A fourth, more subtle case worth flagging: when a real package has been deprecated and split into two scoped successors, the agent often hallucinates a hybrid name that combines fragments of both. If your repo still references the deprecated package, you may see this pattern grow as you migrate.
A five-minute audit before you run install
Before you reach for pnpm install on agent-generated code, scan every external import against both package.json and the npm registry. The script below separates phantom imports (nowhere on npm) from ordinary missing dependencies, so you know which lever to pull. The point is not just to fail loudly — it's to label why a dependency is missing, because the fix is completely different.
# scripts/scan-phantom-imports.sh
# Usage: bash scripts/scan-phantom-imports.sh src
set -euo pipefail
TARGET="${1:-src}"
# 1. Pull external package names out of every import statement
mapfile -t IMPORTS < <(
grep -rEho "from ['\"][^'\"./][^'\"]*['\"]" "$TARGET" \
| sed -E "s/from ['\"]([^'\"]+)['\"]/\1/" \
| awk -F/ '/^@/ {print $1"/"$2; next} {print $1}' \
| sort -u
)
# 2. Cross-check against package.json AND the npm registry
for pkg in "${IMPORTS[@]}"; do
in_pkg=$(jq -r --arg p "$pkg" '
(.dependencies // {}) + (.devDependencies // {}) | has($p)
' package.json)
exists=$(npm view "$pkg" name 2>/dev/null || echo "")
if [ "$in_pkg" = "false" ] && [ -z "$exists" ]; then
echo "🚨 PHANTOM: $pkg (not in npm registry, not in package.json)"
elif [ "$in_pkg" = "false" ]; then
echo "⚠️ MISSING DEP: $pkg (exists on npm, not yet installed)"
fi
doneExpected output looks like this — PHANTOM lines are hallucinations, MISSING DEP lines are ordinary forgotten installs:
🚨 PHANTOM: react-hook-toast (not in npm registry, not in package.json)
⚠️ MISSING DEP: zod (exists on npm, not yet installed)
I keep this script in scripts/ and reference it from AGENTS.md so the agent runs it before proposing changes. Catching phantoms at the diff stage is far cheaper than after a failed install — or, worse, after a typosquat package has already landed in your lockfile. The script also runs in CI as a final guard, fed by git diff --name-only origin/main HEAD, so a phantom can never reach main even if a reviewer waves it through.
If you prefer to integrate with eslint, the rule import/no-unresolved plus eslint-plugin-import-newlines will catch phantoms statically. The shell script earns its keep when you want zero-config detection that works on any branch, including ones where eslint isn't even configured yet.
Telling Antigravity to stop guessing
Detection is half the answer. The other half is constraining the model so it stops inventing names in the first place. The rules I keep at the top of every AGENTS.md look like this. The Manager Surface gives this file higher priority than ordinary docs, so guidance here actually moves the needle.
## Dependency Discipline
- Never invent a package name. If you are not 100% sure a package exists on npm,
call the `pnpm search` tool first and quote the registry response.
- Always update `package.json` and run `pnpm install` *in the same task*.
Do not split "edit code" and "install deps" across separate tasks.
- If the user requests a feature that needs a new dep, list 2 candidate
packages with weekly download counts before choosing one.Pair that with Settings → AI Behavior → Tool Permissions, where I whitelist pnpm search and npm view for every agent. Once the agent can verify cheaply, "imagine and write" turns into "look up and write" — which is, in the end, what we want. The same approach extends to Python (pip index versions) and Go (go list -m -versions), so you can apply the same shape of rule across polyglot repos.
If you run a multi-agent setup, give the reviewer agent a separate prompt that rejects any dependency-touching PR that hasn't passed the phantom scan. This role separation is the same pattern I described in my post on agents that ignore instructions, applied here to supply-chain hygiene rather than to coding style.
What to do after a phantom slipped through
Sometimes you will only notice the problem after pnpm install succeeded — which means a real package occupied the name you trusted. Treat that as a potential typosquat or slopsquat hit and run these three checks immediately:
pnpm why <pkg>to see exactly where the dependency entered your treepnpm audit --prodto compare advisories before and after the change- Open
node_modules/<pkg>/package.json, followrepository.url, and look at the GitHub stars and last commit date with your own eyes
If anything feels off — a freshly registered author, no repository link, suspicious post-install scripts — don't stop at pnpm remove. Run pnpm store prune and delete node_modules and .pnpm-store outright, then rebuild from a clean lockfile review. If you maintain an SBOM, diff cyclonedx-pnpm output in CI so unfamiliar names cannot land silently. I documented the full triage flow in the module and package error guide.
A short field guide to the names that get hallucinated
Over a year of running this scan I've kept a running list of the names that show up most often as phantoms. The patterns repeat enough that you can recognize them by sight after a while:
- Hyphen-spliced hybrids —
react-hook-toast,next-auth-helpers,tailwind-merge-cn. Two real packages welded together at a common token. - Plausible scoped packages —
@vercel/edge-cache,@nextjs/auth. The scope and short name read like an official combination, but the publisher never claimed the name. - Verb-noun coinage —
validate-zod,format-date-fns. Built from a real library plus a verb that fits the prompt's intent. - Backported "v2" names —
react-query-v5,zod-v4. The model knows a major version exists but invents a separate package instead of bumping the version range.
When a phantom looks like one of these patterns, it's almost always safe to ask the agent for the closest real package and continue. Save the more skeptical investigation for hits that don't match any of the patterns above — those are more likely to be genuine typosquats.
Wrapping up
Next time the AI hands you an import you don't recognize, don't trust it on faith — diff it against package.json and the npm registry. Save the script above as scripts/scan-phantom-imports.sh today, and add a single line to your AGENTS.md that says "run this before any code suggestion." Detection and prevention together cost less than one debugging session, and you get back the part of every workday that used to disappear into chasing imports that were never there to begin with.