Kill the port, not the process name

Checked 22 Sep 2026 · By Luke Czak

ArticleHow to Use AIFree to read

A broad pkill that matches on command line text takes out unrelated terminals along with the one you meant to stop.

I killed three of my own background monitors once with a cleanup command I was sure was scoped correctly. I had asked an agent to stop a stuck dev server, and it reached for pkill with the flag that widens the match from the process name to the entire command line, so the pattern caught every process whose invocation happened to contain the same directory string. The dev server, yes. Also two long-running monitors and a watcher I had left going in another window, none of which had anything to do with the thing I was trying to stop.

The instinct that produces this bug is reasonable on its face. You want to stop "the node process" or "the thing on port 3000", and pkill with a name fragment feels like the direct way to say that. The problem is that the flag which makes pkill actually find your process — the one that matches against the whole command line rather than the bare binary name — is the same flag that makes it match everything else sharing a word with it, and command lines are full of shared substrings: a project directory name, a script name, an interpreter invocation that a dozen other processes also happen to include. The pattern that looks specific to the thing you're debugging is rarely specific in practice, and it looks specific for exactly the reason it isn't: you wrote it while looking at one terminal, thinking about one process, with no visibility into what else on the machine happens to share a word with it.

The fix is to stop asking "what is this process called" and start asking "what is it holding". A port is a much narrower question. lsof -ti:3000 gives you the processes holding that port, with no ambiguity about which of your fifteen open terminals they belong to — and adding -sTCP:LISTEN narrows it to the one actually bound to the port, rather than that one plus whatever happens to be connected to it. Pipe that into kill and you have touched exactly the thing you named. If there's no port to key on, pkill -x with the exact process name (not a fragment) is the next-narrowest tool, because -x requires an exact match rather than a substring match.

What makes this worth writing down rather than just fixing quietly is that it is a blast-radius problem, and blast-radius problems don't announce themselves before they happen. A deploy script that touches the wrong environment fails loudly and immediately. A cleanup command that kills the wrong process fails silently from the perspective of the person running it — the command reports success, the intended process is dead, and the two or three other things it also caught go unmentioned in any output you're looking at. You only find out later, when you go looking for something that should have been running and it simply is not there.

This generalises past pkill. Any command whose target selector is "text that looks like it identifies the right thing" rather than "a handle that can only resolve to the right thing" carries the same risk — bulk file deletes by name pattern, container stops by image name substring, log purges by date-ish string matching. The question to ask before running any of them is not "does this pattern match what I want" but "what else could this pattern match, and have I actually checked." A pattern match is a guess dressed up as a selector, and guesses that run with kill permissions are the ones that cost you work that was already running. The cheap habit that catches most of these before they run is to print the match first — list the PIDs and full command lines a pattern would hit, read the list, and only then convert the same pattern into the actual kill. That one extra read is the whole difference between a targeted stop and a blast radius you find out about later.

I now treat "match on a name" as the fallback, never the default, for anything destructive. The default is the narrowest handle the system actually gives you — a port, a PID you looked up explicitly, an exact process name with an exact-match flag. It takes one extra command to look the handle up first. That command has never once cost me anything. The pattern match that skipped it has cost me hours of work that was already running, more than once, which is a much worse trade than the ten seconds saved.

Comments (0)

Sign in to comment.