My Mac said it was out of memory. It was lying, sort of.

Posted by Michael S. on June 10, 2026

The dialog every Mac user dreads popped up the other night: "Your system has run out of application memory." 16 GB of RAM, and macOS was begging me to start force-quitting apps.

So I did the obvious thing. Checked memory pressure. It said 72% free.

That's the part that bugged me. I added up everything running: a handful of CLI processes at a few hundred MB each, a browser, an Electron app. Call it 5 GB, generously. On a 16 GB machine. There was no way I was actually out of memory. And yet the dialog kept coming back, every few minutes, like a smoke alarm with a dying battery.

Here's the thing about that dialog: it fires on a spike, not a steady state. By the time you go looking, the system has usually recovered. You're photographing the crime scene after the cleanup crew left. Every measurement I took said "fine," because every measurement landed in the calm between spikes.

The swap numbers were weird too. Only 3 GB of swap on a machine with 570 GB of free disk. macOS should grow swap into the tens of GB before it ever throws that dialog. It wasn't growing. The swapfiles on disk were four days old. Something didn't fit.

So I stopped measuring after the fact and set a watcher running. Sample memory and the single biggest process every five seconds, for ten minutes. Catch it in the act.

It came back almost immediately. And there it was.

grep -rl "def send_telegram_voice" /Users/me/...

One grep. Running for two hours. Sitting on 1.6 GB of RAM and slowly climbing.

I'd actually seen this process in my very first check and dismissed it as noise. I assumed it was an artifact of my own command. It wasn't. Same PID across snapshots taken twenty minutes apart, growing the whole time. A real, wedged, runaway search that had been quietly marching toward OOM the entire evening. Combined with everything else, it was just enough to tip the system over, trigger the kill dialog, drop back, and creep right back up again.


Why a grep ate 1.6 GB

This is the good part, because the why is genuinely interesting and it's a trap anyone can fall into.

grep is line-oriented. To check whether a line matches, it reads input until it hits a newline, holding that whole line in a buffer so it can scan it. Normal source code has a newline every 40 to 100 bytes, so that buffer stays tiny and gets recycled for the next line. No problem.

But this grep was recursing through my home directory. And my home directory has audio files in it: OGG and Opus voice clips, generated by an automation I'd been building.

Binary audio has almost no newline bytes. A newline is just the byte 0x0A, and in a compressed audio stream it shows up only by random chance, sometimes not at all in an entire file. So grep started reading one of these files looking for the end of "the line"... and never found it. To hold a line that big, it kept doubling its buffer. 64 KB, 128 KB, 256 KB, and on and on, reallocating bigger and bigger until it had swallowed a multi-hundred-MB file as a single "line."

Then it got worse. Even after grep moved on to the next file, the memory didn't come back. The C allocator hangs onto a freed chunk that big as a high-water mark instead of returning it to the OS. So RSS stayed pinned at 1.6 GB and ticked up a little every time it hit an even longer run of newline-free bytes somewhere else in the tree.

Three things stacked up: a line-oriented tool, newline-free binary input, and an allocator that keeps the peak. Each one is harmless alone. Together they turned a one-line search into a slow-motion memory leak.


The fix is one flag

grep -rI --include='*.py' "pattern" .

The -I is the whole game. It tells grep to sniff the first few KB of each file, notice the non-text bytes, and skip the file entirely, before it ever tries to build that buffer. The --include narrows the search to the files you actually care about. Or just use rg (ripgrep), which skips binaries and respects .gitignore by default, so you never trip this in the first place.

I added a note to the project's instructions so I don't do it again. Then I killed the grep, got my 1.6 GB back, and the dialog stayed gone for the rest of the night.


The actual lesson

It wasn't really about grep.

The machine told me a story, "out of memory," and the obvious reading of that story was wrong. My instinct that the numbers didn't add up was the only thing worth trusting. When the headline and the arithmetic disagree, believe the arithmetic and go find the thing you're not seeing.

It's almost always a thing you're not seeing.

Enjoyed this post?

Get notified when I publish something new. No spam, unsubscribe anytime.