ANTIGRAVITY LABJP
Articles/Antigravity Basics
Antigravity Basics/2026-04-29Intermediate

Why Antigravity Agents Trigger So Many EADDRINUSE Errors — A Practical Fix Guide

If your Antigravity agent keeps crashing dev servers with EADDRINUSE: address already in use, the cause usually traces to three structural issues — terminal-tab churn, background processes, and post-HMR zombie processes. Here is the diagnostic flow and the permanent fixes I rely on.

antigravity429eaddrinuseporttroubleshooting105dev-server2

How many times today have you stared at Error: listen EADDRINUSE: address already in use :::3000? If you switched from VS Code to Antigravity recently, you have probably noticed the frequency at least doubled. After half a day on a Next.js project I caught myself running lsof -i:3000 | xargs kill for the fifth time, which is what kicked off this investigation.

The error itself is simple OS-level signal: someone else is already listening on the port. What is interesting is why Antigravity workflows produce so many of them. There are three structural pitfalls in agent-driven development that quietly accumulate zombie processes throughout the day. Let me walk through what they are and the permanent fixes that have stopped the bleeding for me.

Why EADDRINUSE Hits Antigravity Users Harder

When you drive an editor by hand, you are the one stopping the dev server. You hit Ctrl+C, watch the prompt return, and only then move on. That habit prevents accidental double-binding almost completely.

Agents do not always inherit that discipline. When you give Antigravity a new instruction, the agent often opens a fresh terminal tab to run npm run dev again — leaving the previous server alive in another tab. That is where the chain of failures starts.

In practice, three patterns repeat:

  • Tab-churn zombies: the agent restarts in a new tab without closing the old one
  • Background-execution leaks: processes started with & or nohup outlive the terminal
  • Post-HMR zombies: an exception during hot reload kills the parent but leaves a child holding the port

Each requires a different remedy, so the first job is figuring out which of the three you are dealing with.

Cause 1: The Agent Restarts in a New Tab Without Closing the Old One

This is the most common scenario. After asking the agent to "switch from port 3000 to 4000 and restart," scroll down the terminal panel and you will often see the original npm run dev tab still happily running.

To verify, click the dropdown at the top right of the terminal panel — or run Terminal: Show All Terminals from the command palette (Cmd+Shift+P). If you see two or more dev tabs, that is your culprit.

Quick fix

# 1. Find the process holding port 3000
lsof -i:3000 -t
 
# 2. Send a polite TERM signal first (preferred)
kill -15 $(lsof -i:3000 -t)
 
# 3. If it doesn't free up after 30 seconds, force-kill
sleep 30 && kill -9 $(lsof -i:3000 -t) 2>/dev/null
 
# Expected output: empty (port is free)
lsof -i:3000

Try -15 (SIGTERM) before -9. Next.js and Vite handle graceful shutdown by releasing file locks correctly. Going straight to -9 can leave half-written cache binaries inside .next or node_modules/.cache, which then triggers a different error on the next startup.

Permanent fix: write the restart protocol into AGENTS.md

Add an AGENTS.md to the project root that spells out how the agent should restart the server. This single change cut my agent-induced EADDRINUSE incidents by roughly 80% across the four Dolice Labs sites.

# Dev server protocol
 
## Before starting the server
1. Run `lsof -i:3000 -t` to check what is listening
2. If a PID is returned, run `kill -15 $(lsof -i:3000 -t)` to stop it
3. Wait 5 seconds, then run `npm run dev`
 
## When restarting
- Close the existing `npm run dev` tab before opening a new one
- If you must use a different tab, confirm the old one is stopped first

Cause 2: Background Processes That Outlive the Terminal

When you ask the agent to "start the server and keep working on something else," it sometimes detaches the process with npm run dev &. Once that happens, even a full Antigravity restart will not clean things up — the OS keeps the orphaned Node process around, and the very next command you run will fail with EADDRINUSE.

Sweep across all Node processes

When you do not know the port number, this one-liner is invaluable:

# List Node processes with their ports
ps aux | grep -E "node|next|vite" | grep -v grep
 
# Bulk cleanup (warning: stops dev servers in other projects too)
pkill -15 -f "next dev"
pkill -15 -f "vite"
 
# Expected output: nothing should be left listening
lsof -i -P | grep LISTEN | grep node

Be careful with pkill -f. The regex is looser than you might expect, and pkill -15 -f "node" will happily kill long-running batch jobs from other projects. Always include a framework-specific token like next dev or vite.

Forbid & in AGENTS.md

## Forbidden launch patterns
- `npm run dev &` (background execution)
- `nohup npm run dev` (survives terminal close)
- `disown`, `screen`, or `tmux` for persistence
 
Long-running processes must run in the foreground in their own terminal tab. Keep the tab open and continue giving instructions from another tab.

Cause 3: Post-HMR Zombie Processes

The third case is trickier and not really the agent's fault. When Vite or Next.js HMR crashes after a large refactor, the parent Node process appears to terminate while a child holding the port silently survives. macOS Activity Monitor often misses it; you only spot it through lsof.

# Show full process info for port 3000
lsof -i:3000 -P
# e.g. node    91234  user   24u  IPv6  0xabc...  0t0  TCP *:3000 (LISTEN)
 
# Inspect the process tree (macOS)
pstree -p 91234
 
# Same on Linux
ps -ef --forest | grep 91234

If a child remains, kill the entire process group instead of one PID:

# Get the process group ID
PGID=$(ps -o pgid= -p 91234 | tr -d ' ')
# Stop the whole group
kill -15 -- -$PGID
 
# Expected: the dev server and all its children are stopped

Make your dev server resilient to port collisions

If the dev server simply falls back to another free port, EADDRINUSE turns from a blocker into a soft notification. For Next.js, edit package.json:

{
  "scripts": {
    "dev": "next dev -p 3000 --turbo || next dev -p 0 --turbo"
  }
}

The -p 0 flag tells the OS to assign any available port. For Vite, set strictPort in vite.config.ts:

// vite.config.ts
import { defineConfig } from "vite";
 
export default defineConfig({
  server: {
    port: 3000,
    strictPort: false, // ← the important line
  },
});

With strictPort: false, Vite walks 3001, 3002, and so on if 3000 is busy. The default true aborts with EADDRINUSE — a poor fit for agent-driven workflows.

A Pre-Start Cleanup Script You Can Reuse

I keep scripts/dev-clean.sh in every project. Running it before the dev server gives me a reliable starting state in the morning.

#!/usr/bin/env bash
# scripts/dev-clean.sh
# Frees ports commonly used during development before launch
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 "🧹 freeing 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 "✅ all ports clean. start the dev server now."

Make it executable, then wire it into package.json:

{
  "scripts": {
    "dev:clean": "bash scripts/dev-clean.sh && npm run dev"
  }
}

Tell the agent to use npm run dev:clean instead of npm run dev in AGENTS.md, and every restart begins from a clean slate.

Related Reading

These cover adjacent territory and pair well with this guide:

Start with One Habit: Run lsof Before Every npm run dev

EADDRINUSE looks trivial in isolation, but in agent-driven workflows it is structurally easy to repeat. Try one thing this week — run lsof -i:3000 before npm run dev, every single time. The vague "why does this keep failing?" feeling becomes a clear "here is what is actually happening on my machine," and from there the rest of the fixes in this guide slot in naturally.

Share

Thank You for Reading

Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

Antigravity2026-05-31
Why Your Antigravity Agent Stops Mid-Task with 429 RESOURCE_EXHAUSTED, and How to Fix It
When you hand a long task to an Antigravity agent, it sometimes halts halfway with a red 429 RESOURCE_EXHAUSTED. That is a rate-limit or quota signal, not a bug. Here is how I diagnose the three flavors of 429 in production, and how to keep your agent from stalling on the same wall twice.
Antigravity2026-05-28
Fixing Antigravity Google Sign-in That Won't Return From the Browser
When you press Sign in with Google in Antigravity and the browser just hangs without returning to the editor, the cause is rarely a single thing. Here is how I narrow it down across four layers — callback port, third-party cookies, stale session files, and network path — using only commands I keep close on every machine.
Antigravity2026-05-25
Why Antigravity Agents Hang on ssh, sudo, and git rebase -i — Diagnosing and Permanently Fixing the Missing TTY Problem
When you ask an Antigravity agent to deploy, the terminal panel stops on 'password:' and never moves. The agent isn't broken — the shell behind it has no PTY, so interactive prompts have no one to answer them. Here's how to diagnose and remove every interactive command from your agent workflow.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →