Recently I’ve been running swarms of coding agents on my laptop. At some point I started to feel like my laptop is haunted, being puppeted by many beings out of my control.

For example, I’ll go to develop on jep, and run into:

% npm run dev

> dev
> react-router dev

Port 3000 is in use, trying another one...
  ➜  Local:   http://localhost:3001/
  ➜  Network: use --host to expose
  ➜  press h + enter to show help

Hey! Who is using localhost:3000 ?!

How to use whoport

whoport is my new small, sharp tool to solve this. Pass in a port and it’ll tell you what command (and therefore what path -> project + worktree) is using it:

% whoport 3000
PORT   PID      COMMAND
3000   61981    node ~/conductor/workspaces/echo/rome-v3/target/debug/api

whoport can look up multiple ports at once:

A Solarized Light terminal showing whoport output for six development processes in city-named Conductor worktrees

Check it out here on GitHub 🐈‍⬛

GitHub - cmnord/whoport: Who's using this port? Who's using this port? Contribute to cmnord/whoport development by creating an account on GitHub. https://github.com/cmnord/whoport GitHub - cmnord/whoport: Who's using this port? preview

After you’ve found the process using your port, you can obviously kill -9 it 🔪 or otherwise shuffle ports around.

Why? ps + grep falls apart with coding agents

My previous tool for this was good ol’ ps with some text searches. ps (“process status”) lists information about processes running on your computer. The typical command is ps aux to list all running processes. I used this in combination with a search tool like grep (or ag or, lately, rg) like so:

 Atuin v18.19.0                                                               <esc>: exit, <tab>: edit, <enter>: run, <ctrl-o>: inspect                                                        history count: 18631
  Search │ Inspect

  3 111ms   3mo ago ps aux | rg 'expo start'
  2 101ms   3mo ago ps aux | rg 'dev:'
  1 128ms   2mo ago ps aux | rg 'expo'
  > 123ms   2mo ago ps aux | rg 'expo '
 [     GLOBAL     ] 'ps aux'
 ps aux | rg 'expo '

shoutout to atuin

ps aux | rg 'npm ' worked when I had a hunch about the name of the command and I could search them. But now I have like 20–50 node processes running on my computer at any time…

% ps aux | rg 'node' | wc -l
      53
% ps aux | rg 'npm ' | wc -l
      28

translation: count how many running processes begin with 'node ' or 'npm '

Moreover, the commands agents kick off are completely unreadable…

% ps aux | rg 'npm ' | head -n 2
cnord            21752   0.1  0.4 446355296 149152   ??  S     8:02PM   0:00.22 node /Users/cnord/.nvm/versions/node/v22.22.3/bin/pnpm -C packages/editor exec vitest run --environment jsdom src/note/index.test.ts src/plugins/doc-change-listener.test.ts

Okay, seems like it’s running Vitest, that’s fine.

cnord             6522   0.0  0.0 435341104   5136   ??  S     7:46PM   0:00.01 /usr/bin/open -n -W -o target/debug/example-dev.stdout.log --stderr target/debug/example-dev.stderr.log --env RANDOM_CONFIG_VAR=foo --env VITE_API_URL=https://api.example.com --env YOUR_SECRET_KEY=hunter2 --env YOUR_RANDOM_OAUTH_CLIENT_ID=hunter2 --env PATH=./my/whole/hecking/path/list/.bin:/......... target/debug/Example.app

What in the world is happening in this 3,000-character command which has my whole env and PATH in it???

which of you is hogging my port?

whoport under the hood

whoport glues together ~two other tools: stalwart ps, plus lsof to list open files.

The core lsof command is:

lsof -t -nP -iTCP:"$port" -sTCP:LISTEN

Brief explanation of the command:

flag(s) meaning
-t terse output: PIDs only, no header
-nP skip DNS resolution and service name lookups
-iTCP:$port only TCP sockets using this port (local or remote)
-sTCP:LISTEN only TCP sockets in listen state

So with just this command we can get a newline-separated list of process IDs (PIDs).

whoport turns this into a comma-separated list to hand to ps.

Heroic ps returns, instead filtering by PID instead of search on the commands!

% ps -o pid=61981,command=COMMAND -p 61981
61981 COMMAND
61981 node ~/conductor/workspaces/echo/rome-v3/target/debug/api

So now I know that the rome-v3 echo worktree is using the port! And from there I can *ahem* terminate it or move aside. localhost:3001 isn’t so bad, anyway.

Filling out your tool belt

I keep little scripts like this in my dotfiles repo, which documents my favorite terminal commands and setup across laptops and machines. 🛠️

You should too! Some influential blogs on this topic:

Read more about tiny personal programs here:

And to learn more about the command line, ps, etc., I highly recommend The Missing Semester of Your CS Education.

Happy coding :)