Ports & dev servers

How to find orphaned Node processes on your Mac

You closed the terminal, but node is still holding port 3000 and 600 MB of RAM. When a process’s parent goes away, macOS hands it to launchd (PID 1). That makes orphans easy to find: they’re dev tools whose parent PID is 1.

List the orphans

Show your own processes whose parent is launchd, filtered to common dev tools:

ps -xo pid,ppid,etime,rss,command \
  | awk '$2 == 1' \
  | grep -Ei 'node|bun|deno|vite|esbuild|tsc|next|python|ruby|uvicorn|gunicorn' \
  | grep -v '/Applications/'
  PID  PPID     ELAPSED    RSS COMMAND
48211     1  2-03:14:09 412880 node /Users/you/code/acme-web/node_modules/.bin/next dev
51007     1    05:41:22  98304 node /Users/you/code/api/node_modules/.bin/tsc --watch
51120     1    05:41:20  61440 /Users/you/.../esbuild --service=0.24.0 --ping
  • -x includes processes with no controlling terminal, which is exactly what an orphan is. Without -a, ps lists only your user’s processes, so nothing from the system appears.
  • ELAPSED is days-hours:minutes:seconds. A next dev running for two days is almost certainly forgotten.
  • RSS is in kilobytes: 412880 is about 400 MB.

The quicker version with pgrep:

pgrep -u "$USER" -P 1 -lf 'node|vite|python'

-P 1 matches only children of PID 1, -f matches against the full command line, and -l prints it.

See how much memory they hold

ps -xo ppid,rss,command | awk '$1 == 1 && /node/ && !/Applications/ {s += $2} END {printf "%.0f MB\n", s/1024}'

Check before you kill

An orphan is a candidate, not a verdict. For each PID, look at where it runs and whether it’s serving anything:

lsof -a -p 48211 -d cwd -Fn | tail -1     # project folder
lsof -nP -a -p 48211 -iTCP -sTCP:LISTEN    # ports it holds

Leave alone anything you started on purpose with nohup or pm2, anything your editor manages, and anything not in your own projects.

Stop them

One at a time:

kill 48211 51007 51120

kill sends SIGTERM, which lets each one exit cleanly. If one ignores it after a few seconds, use kill -9 <PID>.

Avoid pkill node or killall node. They match every Node process you own, including the ones your editor’s language servers, Electron helpers or a running agent depend on. If you want a bulk command, restrict it to orphans and check the list first by running the pgrep line above without the kill:

pgrep -u "$USER" -P 1 -f 'vite|next dev|tsc --watch' | xargs kill

Why they happen

Most orphans come from one of these:

  • Closing the window instead of Ctrl+C. Usually the shell forwards the hang-up and the server dies. Not always: watchers, background jobs and processes started with nohup keep going.
  • A killed parent. npm run dev starts node, which may start its own workers. Kill the npm process with -9 and its children get re-parented to launchd instead of stopping.
  • Helper services. esbuild, Turbopack, the TypeScript server and some test runners spawn long-lived service processes that sometimes outlive the tool that started them.
  • AI agents. Claude Code, Codex and Cursor run dev servers and watchers to check their own work. When the session ends, anything detached stays behind. See cleaning up after Claude Code.

Stop making new ones

  • Press Ctrl+C before closing a tab that runs a server. Terminal and iTerm2 warn when you close a window with a running process; leave that warning on.
  • Run long-lived servers in one place you’ll notice, such as a tmux session or your editor’s task runner, rather than scattered tabs.
  • Use trap 'kill 0' EXIT at the top of shell scripts that start background jobs, so the jobs stop when the script does.

An orphan often shows up first as a port conflict. If you got here from EADDRINUSE, see address already in use on Mac.

Doing this with Stat Space

Stat Space’s orphan sweep runs this check continuously. It looks only at dev-tool processes (node, python, vite, tsc and similar) re-parented to launchd after their terminal or agent went away, and shows the total, for example “Free 2.1 GB · kill 7”. Each row has its age, memory and any ports. It never touches system processes and always asks before stopping anything.

From the terminal, statspace orphans prints the list and statspace orphans --kill stops them after a confirmation. It’s part of the free Agents tab.

Questions

Is every process with parent PID 1 an orphan?

No. Every app you open from the Dock or Finder, and every LaunchAgent, has launchd as its parent. That’s why you filter to dev-tool command lines like node, vite or python and skip anything under /Applications.

Why didn’t closing the terminal stop my dev server?

Closing a window sends a hang-up signal to the shell, which passes it to its jobs. Processes started with nohup, & plus disown, tools that ignore the hang-up, and grandchildren whose middle parent was killed with kill -9 all survive it.

Are processes in tmux or screen orphans?

No. Their parent is the tmux or screen server, which is still running. Reattach with tmux attach and stop them there.

Get started

Know what your Mac is doing, without adding to it.

Free, no account. Pro is $9.99 once for the first 100 buyers.