「Error: listen EADDRINUSE: address already in use :::3000」という赤い文字列を、1日に何度も見ていませんか。Antigravity でエージェントに修正を任せながら開発していると、なぜか普段の VS Code 作業の倍以上の頻度でこのエラーに出くわします。私自身、Next.js のプロジェクトを Antigravity で半日触っただけで、lsof -i:3000 | xargs kill を5回以上叩いていることに気づき、これは何かおかしいと感じたのが調査のきっかけでした。
このエラー自体は OS レベルの「すでに誰かが同じポートを使っている」という単純な合図です。問題なのは、Antigravity でエージェントを使った開発フローに 構造的な3つの落とし穴 があり、それが連鎖的にゾンビプロセスを生み出してしまうことです。ここでは私が試行錯誤の末に整理した原因の切り分けと、エージェントに同じエラーを繰り返させない恒久的な対策をご紹介します。
なぜ Antigravity でだけ EADDRINUSE が頻発するのか
通常のエディタで開発しているとき、開発サーバーを止めるのは自分自身です。「Ctrl+C を押して、確認してから次のコマンドを叩く」という習慣で、ポートが二重に握られることはほとんどありません。
ところが Antigravity のエージェントは、自分が起動したプロセスを「自分のターミナルで自分が止めるべきもの」と認識しないことがあります。エージェントは新しい指示が来ると、別のターミナルタブを開いて npm run dev を打ち直すことが多く、結果として古いサーバーが裏で生き続けてしまいます。これが構造的な原因の入り口です。
具体的には、次の3つのパターンが繰り返し起きます。
- タブ切り替え型のゾンビ化: エージェントが古いタブを閉じずに新しいタブで再起動する
- バックグラウンド実行の取り残し:
&やnohupを使った起動が、ターミナル終了後も残る - HMR クラッシュ後の握り直し失敗: ホットリロード中の例外で、ポートだけ握られた状態の Node プロセスが残る
それぞれ対処法が異なるので、まず自分がどのパターンに当たっているかを切り分けます。
原因1: エージェントが古いタブを閉じずに別タブで再起動している
最も多いパターンです。エージェントに「ポートを 3000 から 4000 に変えて再起動して」と頼んだ直後、ターミナルパネルを下にスクロールすると、古い npm run dev が動いているタブがそのまま残っているのを見つけることがあります。
確認するには、ターミナルパネル右上のドロップダウン(または Cmd+Shift+P から Terminal: Show All Terminals)で、現在開かれているターミナルセッション一覧を表示します。dev と書かれたタブが2つ以上あれば、これが原因です。
すぐ効く対処
# 1. 現在 3000 番を握っているプロセスを特定
lsof -i:3000 -t
# 2. プロセスを優しく止める(推奨)
kill -15 $(lsof -i:3000 -t)
# 3. 30秒待って解放されなければ強制終了
sleep 30 && kill -9 $(lsof -i:3000 -t) 2>/dev/null
# 期待する出力: なし(コマンドが空で返れば解放成功)
lsof -i:3000-15(SIGTERM)で先に試すのは、Next.js や Vite が graceful shutdown でファイルロックを正しく解放するためです。いきなり -9 を叩くと、.next や node_modules/.cache 内に書きかけのバイナリファイルが残り、次回起動時に別のエラーを引き起こすことがあります。
恒久対策: AGENTS.md でエージェントの再起動手順を明示する
プロジェクトルートに AGENTS.md を作り、再起動の作法をエージェントに明示しておくと、二重起動の発生率がぐっと下がります。
# 開発サーバー操作のルール
## サーバー起動前に必ず実行
1. `lsof -i:3000 -t` で現在の使用状況を確認する
2. プロセスが見つかった場合は `kill -15 $(lsof -i:3000 -t)` で停止する
3. 5秒待ってから `npm run dev` を起動する
## サーバー停止時
- 既存の `npm run dev` タブを閉じてから別タブを開く
- 別タブで再起動する場合は、必ず古いタブの停止を確認してから私が運用している Dolice Labs の 4 サイトでは、この AGENTS.md を入れてからエージェント起因の EADDRINUSE が体感で 8 割減りました。
原因2: ターミナルが閉じてもプロセスが残るバックグラウンド実行
「サーバーを起動してそのまま別の作業をさせて」と頼むと、エージェントは npm run dev & のようにバックグラウンド実行で逃がしてしまうことがあります。これをやられると、Antigravity を再起動しても親プロセスから切り離された Node プロセスが OS に残り、再起動直後の最初のコマンドが必ず EADDRINUSE で落ちます。
全プロセスを横断的に確認する
特定のポート番号がわからない時のために、Node プロセスをすべて洗い出すワンライナーを覚えておくと便利です。
# Node プロセスをポート付きで一覧
ps aux | grep -E "node|next|vite" | grep -v grep
# まとめてクリーンアップ(注意: 他のプロジェクトの dev も止まる)
pkill -15 -f "next dev"
pkill -15 -f "vite"
# 期待する出力: 何も表示されなければ完全に停止している
lsof -i -P | grep LISTEN | grep nodepkill -f の正規表現は意外と緩いので、pkill -15 -f "node" のような書き方は別プロジェクトの長時間バッチを巻き込むことがあります。プロセス名は最低でも next dev や vite のようにフレームワーク固有の文字列を含めるのが安全です。
エージェントに & を禁止する
AGENTS.md に以下を追記しておきましょう。
## 禁止する起動方法
- `npm run dev &`(バックグラウンド実行)
- `nohup npm run dev`(端末切断後も残る起動)
- `disown` / `screen` / `tmux` を経由した永続化
長時間動かしたいプロセスは、必ずフォアグラウンドで起動し、専用ターミナルタブを残して指示を続けてください。原因3: HMR クラッシュ後のゾンビプロセスが残留する
3つ目はもっとも厄介で、エージェントが悪いわけではないケースです。Vite や Next.js の Hot Module Replacement が大規模なリファクタの直後に内部例外で落ちると、メインの Node プロセスは終了したように見えても、ポートを握っていた子プロセスが残ることがあります。
これは macOS の Activity Monitor を開いても気づきにくく、lsof で初めて発覚します。
# ポート別にプロセスツリーごと表示
lsof -i:3000 -P
# 例: node 91234 user 24u IPv6 0xabc... 0t0 TCP *:3000 (LISTEN)
# プロセスツリーを確認(macOS)
pstree -p 91234
# 同様に Linux
ps -ef --forest | grep 91234子プロセスが残っている場合は、プロセスグループ単位で止めるのが確実です。
# プロセスグループ ID を取得
PGID=$(ps -o pgid= -p 91234 | tr -d ' ')
# プロセスグループごと停止
kill -15 -- -$PGID
# 期待する動作: 子プロセスを含む dev サーバーがすべて停止恒久対策: 開発サーバーをポート競合に強くする
そもそもポートが衝突しても自動で別のポートに逃げてくれれば、エラー自体が事故ではなく「ちょっとした通知」になります。Next.js の場合は package.json を以下のように変更します。
{
"scripts": {
"dev": "next dev -p 3000 --turbo || next dev -p 0 --turbo"
}
}-p 0 は「OS に空きポートを割り当てさせる」という指定で、3000 番が埋まっていても自動で別のポートで起動します。Vite なら vite.config.ts で同じことができます。
// vite.config.ts
import { defineConfig } from "vite";
export default defineConfig({
server: {
port: 3000,
strictPort: false, // ← これが重要
},
});strictPort: false にしておくと、3000 番が埋まっていれば 3001、3002 と順に空きを探します。strictPort: true のままだとそのまま EADDRINUSE で落ちる仕様なので、Antigravity でエージェントに任せる開発では false 推奨です。
即座にポートを解放するための起動前スクリプト
毎朝の作業開始時に走らせる「お掃除スクリプト」を作っておくと、心理的に楽になります。私は scripts/dev-clean.sh として保存しています。
#!/usr/bin/env bash
# scripts/dev-clean.sh
# 開発でよく使うポートを掃除してから起動するヘルパー
set -euo pipefail
PORTS=(3000 3001 4000 5173 8000 8080)
for PORT in "${PORTS[@]}"; do
PIDS=$(lsof -ti:$PORT 2>/dev/null || true)
if [ -n "$PIDS" ]; then
echo "🧹 port $PORT を解放中(PID: $PIDS)"
kill -15 $PIDS 2>/dev/null || true
sleep 1
# 残ったら強制終了
REMAIN=$(lsof -ti:$PORT 2>/dev/null || true)
[ -n "$REMAIN" ] && kill -9 $REMAIN 2>/dev/null || true
fi
done
echo "✅ 全ポート解放完了。dev サーバーを起動してください"実行権限を付けて、package.json に登録します。
{
"scripts": {
"dev:clean": "bash scripts/dev-clean.sh && npm run dev"
}
}エージェントへの指示も npm run dev ではなく npm run dev:clean を使うように AGENTS.md に書き加えれば、起動の度に自動でクリーンになります。
関連記事
トラブルシューティングの隣接領域として、次の記事も合わせてご覧いただくと役立つはずです。
- Antigravity の内蔵ターミナルが起動しない・コマンドが通らないときの診断ガイド
- Antigravity でホットリロード(HMR)が効かない問題の切り分けと修正手順
- Antigravity の Workspace Indexing が止まる・完了しない時の診断と修復手順
まずは「次の dev 起動前に lsof を1回叩く」習慣から
EADDRINUSE は単発で見ると単なるエラーですが、Antigravity でエージェント開発をしている限り、構造的に再発しやすい問題です。今日試していただきたいのは1つだけ、npm run dev を打つ前に lsof -i:3000 を打つ習慣を1日続けてみてください。「なんとなく毎回起動が失敗する」という曖昧な不快感が、「今、何が起きているか」が見える状態に変わるはずです。そこから先は、本記事の対策を1つずつ取り入れていけば、エージェントとの開発リズムが目に見えて軽くなっていきます。