「pnpm install したら ERR_PNPM_NO_MATCHING_VERSION で止まりました」— Antigravity の AI が書いてくれたコードをそのまま走らせて、こうしたエラーで足止めされた経験はないでしょうか。私自身、Next.js のリファクタリングで react-hook-toast という、それらしい名前のパッケージを当たり前のように import 文に並べられて、最初は自分の知識の方を疑ってしまいました。
実在しないパッケージを AI がしれっと import に書き込む現象は、業界では「Phantom Import」「Slopsquatting の温床」と呼ばれていて、Antigravity に限らず Cursor や Claude Code でも報告されています。ただ Antigravity はマルチエージェントが並列で書き換えるぶん、誰がいつ phantom を入れたかが追いにくく、放置するとビルドだけでなくサプライチェーンの脆さにもつながります。今回はこの現象の発生メカニズムと、エディタ側でできる予防策まで掘り下げて整理します。
なぜ「ありそうなパッケージ名」が生成されるのか
phantom import の根っこには、LLM が「もっともらしさ」を最大化するように訓練されているという事実があります。たとえば「react-hook-toast」は実在しませんが、react-hot-toast・react-hook-form・use-toast といった近接パッケージのトークンが学習データで共起していて、モデルにとっては同じくらい自然に見えてしまうのです。
加えて、Antigravity のエージェントは Manager Surface 経由でファイルを書き換えるとき、package.json の現状を毎回フルに読み込むわけではありません。大規模リポジトリではトークン上限を節約するためメタ情報のみが渡るケースがあり、その状態で「Toast 通知を導入してください」と頼むと、似た名前を当てに行く確率が上がります。
私が観測したかぎりでは、以下の3つの場面で再現性が高いです。
- 一度のプロンプトで複数機能(認証+通知+分析)を追加させたとき
- 既存パッケージのバージョンを大きく上げる作業のついで
- README しかコンテキストに渡していない新しいワークスペース
起きた phantom import を5分で見抜く手順
慌てて pnpm install を走らせる前に、AI が触ったファイルの import 文を一気に検証する癖を付けると安全です。次のスクリプトは、未インストールの依存と package.json 未登録の依存を分けて表示します。何を解決するためのコードかというと、「ファイル上に書かれた import 文」と「実際にインストールできる依存」のズレを可視化するためのものです。
# scripts/scan-phantom-imports.sh
# 使い方: bash scripts/scan-phantom-imports.sh src
set -euo pipefail
TARGET="${1:-src}"
# 1. ソース内の import から外部パッケージ名のみ抽出
mapfile -t IMPORTS < <(
grep -rEho "from ['\"][^'\"./][^'\"]*['\"]" "$TARGET" \
| sed -E "s/from ['\"]([^'\"]+)['\"]/\1/" \
| awk -F/ '/^@/ {print $1"/"$2; next} {print $1}' \
| sort -u
)
# 2. package.json と 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 (npm にも package.json にも無し)"
elif [ "$in_pkg" = "false" ]; then
echo "⚠️ MISSING DEP: $pkg (npm には在るが未インストール)"
fi
done期待する出力は次のようなものです。PHANTOM 行は AI のハルシネーション、MISSING DEP 行は通常の入れ忘れと切り分けられます。
🚨 PHANTOM: react-hook-toast (npm にも package.json にも無し)
⚠️ MISSING DEP: zod (npm には在るが未インストール)
このスクリプトをリポジトリの scripts/ に置いて、エージェントに「変更を提案する前に必ず bash scripts/scan-phantom-imports.sh src を走らせ、PHANTOM がゼロであることを確認してください」とルール化しておくと、未然に防げる確率がぐっと上がります。
Antigravity 側で再発を抑える設定
検出だけでなく、AI の出力そのものに歯止めをかけたいところです。私のプロジェクトでは、ワークスペース直下の AGENTS.md に次のブロックを置いています。Manager Surface のエージェントはこのファイルを優先的に読みに行く挙動があるため、phantom 抑制の効きが体感的に良くなりました。
## 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.さらに、設定 → AI Behavior → Tool Permissions で pnpm・npm のサブコマンドのうち search・view をホワイトリスト化しておくと、エージェントが「想像」ではなく「確認」してから書き始めるようになります。
エージェントを複数走らせている場合は、Manager Surface のレビューエージェントに「依存関係追加 PR は phantom スキャンを通過していなければ却下」というプロンプトを与え、書き手と監査役を分業させるのが効果的です。これは AI エージェントの指示が守られない問題への対処 で触れた「ロール分業によるドリフト抑制」の応用でもあります。
「インストールできてしまった」ときの後始末
phantom と気づかず pnpm install を通してしまった場合、本物そっくりの悪意あるパッケージ(typosquat / slopsquat)を踏んでいる可能性があります。次の3点をその場で確認してください。
pnpm why <pkg>で依存ツリーのどこから入ったかを特定するpnpm audit --prodを走らせて警告が増えていないかを比較するnode_modules/<pkg>/package.jsonのrepository.urlを開いて、GitHub のスター数や最終更新日を目視で確認する
怪しい挙動があれば、pnpm remove だけでなく pnpm store prune と rm -rf node_modules .pnpm-store まで踏んで完全に消し去るのが安心です。SBOM を運用しているなら、CI で cyclonedx-pnpm の差分を見て、知らない名前が紛れ込んでいないかをチェックする運用も合わせておきたいところです。具体的な手順は モジュール・パッケージエラーの恒久対処 でまとめています。
よく出会う「もっともらしい」名前のパターン
scan を1年ほど回し続けて気づいたのですが、phantom にもいくつか定型のパターンがあります。覚えておくと、見た瞬間に「あ、これか」と判断できて時短になります。
- ハイフン継ぎ接ぎ型:
react-hook-toast、next-auth-helpers、tailwind-merge-cnのように、実在する2つのパッケージを共通トークンで溶接した名前 - それっぽいスコープ付き:
@vercel/edge-cache、@nextjs/authのように、公式が出していそうで出していない命名 - 動詞+名詞の造語:
validate-zod、format-date-fnsのように、実在ライブラリ+意図を表す動詞を組み合わせた名前 - バージョン埋め込み型:
react-query-v5、zod-v4のように、メジャー更新を別パッケージとして幻視する名前
このパターンに当てはまる phantom なら、エージェントに「いちばん近い実在パッケージは何?」と聞き直すだけで安全に切り替えられます。逆にどのパターンにも当てはまらない名前は、本物の typosquat の可能性が一段高いので、ストアの過去版確認まで踏み込んでください。
全体を振り返って
phantom import に出会ったら、まず「AI が書いた import 文」を package.json と npm registry に突き合わせて、ハルシネーションなのか単純な入れ忘れなのかを切り分けてみてください。今日からできる一歩としては、上のシェルスクリプトを scripts/scan-phantom-imports.sh として保存し、AGENTS.md に「変更前に必ずこれを走らせる」と一文だけ追記することをおすすめします。検出と抑制の2段構えに切り替えるだけで、ビルドの時間も心の余裕も、目に見えて戻ってきます。