如何找出並停止 Claude Code 和 Codex 留下的開發伺服器
How to find and stop dev servers Claude Code and Codex left running
文章講解如何找出並停止 Claude Code、Codex 等智能體留下的開發伺服器。可用 lsof -nP -iTCP:5173 -sTCP:LISTEN 查找監聽程序。
You run npm run dev and Vite says Port 5173 is in use, trying another one.... You didn't start anything on 5173. An agent did, an hour ago, to check a page. It said the page looked good and moved on. The server didn't.
Here's how to find it and stop it:
-
Find it:
lsof -nP -iTCP:5173 -sTCP:LISTENshows the process listening on the port. Drop the port number to list every listening process. -
Stop it:
kill <PID>. If it's still there a few seconds later,kill -9 <PID>. -
Claude Code:
/taskslists the session's background tasks. Select one and pressxto stop it. -
Codex:
/pslists the session's background terminals, and/stopstops all of them. -
Grok Build:
/tasksorCtrl+Gshows background tasks. Pressxon one to stop it. -
The catch: a server started with
&ornohupinside an ordinary command isn't a background task. None of those lists show it, and it keeps running after the session ends.
Why does it say port 5173 is already in use?
Because something is still listening on it. Dev servers pick a fixed default port: 5173 for Vite, 3000 for Next.js. When it's taken, both quietly move on. Vite tries the next free port and logs Port 5173 is in use, trying another one.... next dev does the same when you didn't set a port, and says Port 3000 is in use by process <pid>, using available port 3001 instead.
That's convenient once. With agents, it adds up. Each one that checks a page starts its own server, and the next one lands on 5174, then 5175. Your browser tab, a callback URL, or a proxy config still points at 5173, which is now serving yesterday's code.
To make Vite stop instead of drifting, set server.strictPort: true in vite.config, or run vite --strictPort. Then a leftover server shows up as an error the first time, not as a mystery an hour later.
Which process is holding the port?
Ask lsof for the process listening on it:
lsof -nP -iTCP:5173 -sTCP:LISTEN
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
node 48213 you 21u IPv6 0x... 0t0 TCP *:5173 (LISTEN)
-sTCP:LISTEN matters. Without it, lsof -i :5173 also lists every program connected to the port, your browser included. That's why the popular one-liner lsof -ti :5173 | xargs kill can kill your browser along with the server.
To see everything that's listening, leave out the port: lsof -nP -iTCP -sTCP:LISTEN. macOS has a few listeners of its own, such as ControlCenter on 5000 and 7000 for AirPlay. Leave those alone.
Then look at where the process came from:
ps -o pid,ppid,lstart,command -p 48213
lsof -a -p 48213 -d cwd -Fn
The first line shows when it started and the full command. The second prints its working directory, which tells you the project. If the parent PID (PPID) is 1, the process that started it has already exited and launchd adopted it. That's what a forgotten background server usually looks like.
Did Claude Code or Codex start it?
The process tree can't tell you once the parent is gone. The environment can. Claude Code sets CLAUDECODE=1 and CLAUDE_CODE_SESSION_ID in every command it runs, and Codex sets CODEX_THREAD_ID. A server inherits them, and they stay with it after its parent exits. On macOS, ps -E prints a process's environment along with its command:
ps -E -o command= -p 48213 | tr ' ' '\n' | grep -E '^(CLAUDECODE|CLAUDE_CODE_SESSION_ID|CODEX_THREAD_ID)='
This loop lists every listening process an agent started, with its port, directory, and session:
for pid in $(lsof -tiTCP -sTCP:LISTEN | sort -u); do
agent=$(ps -E -o command= -p "$pid" | tr ' ' '\n' |
grep -E '^(CLAUDE_CODE_SESSION_ID|CODEX_THREAD_ID)=' | head -1)
[ -n "$agent" ] || continue
port=$(lsof -nP -a -p "$pid" -iTCP -sTCP:LISTEN -Fn | sed -n 's/^n.*://p' | sort -u | tr '\n' ' ')
dir=$(lsof -a -p "$pid" -d cwd -Fn | sed -n 's/^n//p')
echo "$pid port $port $(basename "$(ps -o comm= -p "$pid")") $dir $agent"
done
The session ID is the one claude --resume <id> and codex resume <id> take, so you can reopen the conversation that started the server and ask it what the server was for. ps -E shows the environment the process started with, and only for your own processes. That's all this needs. For Grok Build, use the working directory and the command.
How do I stop it without breaking anything?
Send it a normal terminate signal first:
kill 48213
A dev server catches this, closes its watcher and sockets, and exits. Check with the lsof line again. If it's still listening after a few seconds, force it:
kill -9 48213
Kill the process that's listening, not its parents. When node exits, the npm run dev above it exits too. Killing upward can hit the shell or the agent that's still running in another tab.
Avoid pkill node and killall node. They also stop every editor extension, language server, and MCP server written in Node, and any dev server you meant to keep.
How do Claude Code, Codex, and Grok stop their own background servers?
Each agent keeps a list of the background work it started, and each list has a stop control:
-
Claude Code: Claude starts a long command as a background task, and you can move a running command there with
Ctrl+B./tasks(also/bashes) lists them; select one and pressxto stop it. Tasks stop when Claude Code exits, including processes that detached undersetsidortimeout. If you send the session to the background with/backgroundinstead of exiting, they keep running there. -
Codex: a command that keeps running after Codex stops waiting for it becomes a background terminal, and the footer says how many are running.
/pslists them and/stopstops all of them. Since 0.115, interrupting a turn withCtrl+CorEscdoesn't stop them. Quitting doesn't always stop them: Codex can keep a session running in its local background service, for example when you leave a running turn with Run in background. Run/stopbefore you quit. -
Grok Build: the agent runs commands in the background itself, and
Ctrl+Bsends a running one there.Ctrl+Gor/tasksshows them; pressxon one to stop it. Stopping a task ends its process group, but not descendants that detached withsetsidornohup.
These lists only know about what the agent started as background work.
Why do some servers outlive the session?
Because the agent didn't start them as background work. When it runs npm run dev & as an ordinary command, the command returns at once, the server is handed to launchd, and no task list ever shows it. Quitting the agent doesn't touch it. The same happens when a background task starts its own nohup … &. Claude Code then reports the task as completed, while the children keep running (issue #92399).
Even tracked work gets lost. In one Claude Code issue, a wrangler dev started as a background task kept running for two days at 89% CPU and 2.5 GB of memory after the user moved on. In another, a hung Python process ran for 136 minutes at 16 GB after the session's context was compacted. The user found it with ps aux. Codex users report background terminals that "run forever", too.
Sessions you didn't close count as well. A Claude Code session sent to the background, or a Codex turn left with Run in background, keeps its servers on purpose.
How do I stop agents leaving servers behind?
None of the three vendors publish a rule for this, but you can tell the agent in CLAUDE.md or AGENTS.md:
## Dev servers
- Start dev servers as a background task, never with `&`, `nohup`, or `setsid`.
- Use the project's fixed port and set it explicitly (`vite --strictPort`, `next dev --port 3000`). If the port is taken, tell me instead of picking another.
- Stop any server you started once you've finished checking it.
The first rule keeps the server in the task list, where quitting the agent stops it. The second turns a leftover server into an error you see right away. The third is a request, not a guarantee: a Codex issue reports background jobs left running even after the user said not to. So keep the lsof check around.
Can I see which Terminal started each server?
That's what the Servers section in PonyMux's Inspector does. For each Terminal it lists every port that a process started there is listening on, with the port and the process name. It doesn't matter whether the agent used a background task, &, or nohup. PonyMux marks each Terminal's processes when they start, and the mark stays after the parent exits.
- Click a row to open the server in your browser.
- Hover it to see when and in which directory it started, the processes that started it (
claude › npm › node), and its command. - Click the trash button to stop it. PonyMux sends a terminate signal and forces it after 3 seconds. Your shell and the agent keep running.
When the agent exits, Servers moves up near the top of the Inspector, where a forgotten server is hard to miss. Moving a Terminal to the Trash or Archive stops everything running in it. Servers only covers processes started in PonyMux Terminals; for anything else, use lsof.
For the details, see Servers.
PonyMux is free for macOS: download it here.
來源:DEV Community · dev.to
