olragon

Why did pkill -f cat close every app on my Mac?

🌱 seedlingplanted #terminal#macos#ai#lessons

Written for People who reach for pkill or killall in a terminal, or who let an AI agent run shell commands on their own machine.

I let a few AI agents work overnight on the Mac at home, and one night they closed every app on it with a four-word command.

The numbers below were counted again on that same machine on the morning of October 31, with pgrep. Counting is harmless.

Tiếng Việt: Vì sao pkill -f cat tắt sạch app trên máy Mac?.

What happened at 4 a.m.

I run a small crew of AI agents on a schedule. Each turn, an agent opens a terminal, reads its task, runs commands, checks the result and reports back to me in a message. Most nights pass without a ripple.

On the night of October 13, at 4:12, an agent hit a cat command that hung. It wanted to tidy up, so it typed pkill -f cat. Then it typed it again, to be sure.

That was enough to tell every app on the machine to quit. The browsers, the code editors, the terminals, OrbStack and the 21 containers inside it (Postgres included), the browser automation tool, the bot that sends me messages. And the supervisor process watching that very agent. It sawed off the branch it was sitting on.

The machine didn't crash. WindowServer stayed up, Finder survived, tmux kept all of its hundred-odd sessions. The agent itself was still alive inside tmux, brought OrbStack and the services it needed back up, and told me. I read the message in the morning, half laughing, half chilled.

Why "cat" matched everything

pkill cat (no -f) matches against the process name only. Any name that contains cat gets hit, so it is already wider than you'd think, but it stays in range.

The -f flag changes the game: it matches against the whole command line, path and arguments. And on macOS, nearly every app lives under /Applications. The word Applications contains cat. So pkill -f cat really means "quit everything installed in the Applications folder".

This morning I counted again, only counting, with pgrep and the same pattern:

commandprocesses matched
pgrep -f cat183 of 1,125
of those, with /Applications in the command line130
pgrep -x cat (named exactly cat)0

The last row is the one that got me. At the time I counted, there was no process named cat at all. The agent wanted to hit one target, and the gun was pointed at 183.

A few dozen more matches don't live in /Applications: words like Location, Notification, verification, plus shells whose arguments mention cat somewhere. A process that has nothing to do with cat, just because an argument mentions the word, is on the list too.

Try aiming at the list below. These are the kind of processes a Mac runs every day, with the hung cat on the last line. Press -f, -x, or type another pattern. Nothing really quits.

  • /Applications/Safari.app/Contents/MacOS/Safari
  • /Applications/Google Chrome.app/Contents/MacOS/Google Chrome --type=renderer
  • /Applications/Visual Studio Code.app/Contents/MacOS/Electron
  • /Applications/OrbStack.app/Contents/MacOS/OrbStack
  • /System/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal
  • /System/Library/CoreServices/NotificationCenter.app/Contents/MacOS/NotificationCenter
  • /usr/libexec/locationd
  • /System/Library/CoreServices/Finder.app/Contents/MacOS/Finder
  • /System/Library/PrivateFrameworks/SkyLight.framework/Resources/WindowServer -daemon
  • /usr/sbin/cfprefsd agent
  • /opt/homebrew/bin/tmux new -s work
  • /bin/zsh -c grep -rn cat notes/
  • cat

Writing a rule wasn't enough

My first reflex was to add a line to the agents' rulebook: "don't kill processes by pattern". Then I thought again.

An agent clearing a hung command is the agent least likely to be reading its rules. It is busy putting out a fire. The rules sit at the top of the session, a hundred lines back. People are the same: in a panic, you type the command your fingers know best.

So I added another layer: a hook that runs before every shell command an agent is about to type. Claude Code lets you attach a hook like that. The hook reads the command, and if it has a dangerous shape it blocks it and says why; the command never runs. The whole hook is 43 lines, with 23 tests.

It blocks four shapes:

What's allowed is short: kill <pid> with a pid the agent recorded itself when it started something, or closing exactly the tmux window it opened. A hung command is left to time out, not hunted by name.

This morning it blocked me

While gathering material for this note, the AI that helps me tend the garden ran a grep for the string "pkill -f cat" across some notes. The hook blocked it on the spot.

The hook treats anything after a quote mark as a command that might run. I wrote it that way on purpose, because ssh other-box "pkill -f cat" or bash -c '…' also run what's inside the quotes. The price is that a harmless grep gets blocked too. Typing it differently, splitting the words, gets past it.

I think that's a price worth paying. A false block costs ten seconds. A false pass closes the whole machine at 4 a.m.

That night left one more mark. The supervisor died mid-turn, so nobody released that turn's lock. The next turns kept seeing the lock and skipping, for almost four hours. Now the lock records the supervisor's pid. The next turn sees that the pid is dead and the session is done, and cleans up by itself.

Try it on your machine

Counting only, nothing gets closed:

pgrep -f cat | wc -l
pgrep -fl cat | grep -c /Applications
pgrep -x cat | wc -l

If the first line prints a big number and the last prints 0, you've seen the trap on your own machine. The habit I've kept since: to stop something, pgrep -x or pgrep -fl first, read the list, then kill the exact pid.

What I still don't know

If you also let AI run commands on your machine, how do you fence it in? Write me a letter below.

Write papa dragon a letter

If this note stirred something, write to me here. A letter reaches only me and never shows up on the page by itself. If one is lovely and you allow it, I may quote it under the note, without your name.

What the garden keeps from a letter

Works from a terminal too: curl -d 'msg=…' olragon.com/garden/pkill-cat-en/letter