• The problem. Adding up every app’s memory on one Mac gave 37.04 GB, while the Mac said 29.73 GB was in use. Memory that several apps share gets counted once per app.
  • What we found. Quitting an app gives back less than its row suggests. For one set of apps, our own menu bar promised 10.66 GB releasable when the honest figure was 8.5 GB.
  • What we built. Every Mint screen that promises memory now uses what actually comes back. A row with work behind it, like a terminal running Claude Code, is listed but left unticked.

Before quitting apps to free memory, check what the numbers measure. Adding every app’s reported usage can count shared memory more than once, so that total is not a promise of what you will get back. Our measurements on one Mac show the difference—and why a terminal running a coding agent should not be treated as idle just because it is in the background.

If your Mac is slowing down while an agent is still working, start with the memory review guide for running AI tasks. It walks through memory pressure, the apps you can consider quitting, and how to check the result.

Ask every process on a Mac how much memory it is using, add the answers up, and you get a number larger than the machine. On one afternoon the sum came to 37.04 GB on a Mac that reported 29.73 GB in use. We ran it again on another day: 29.81 against 26.94.

Adding up what every process reportsThe sum of ri_phys_footprint across all processes, against the memory the machine says it is using.One afternoon37.04 GB29.73 GBAnother day29.81 GB26.94 GBSum of what each process reportsWhat the machine is using
The sum is larger than the machine on both runs, by 7.3 GB and 2.9 GB. Nothing is broken. A page that three processes share is counted three times, and a Mac shares a great deal.

Nothing is broken. The number each process reports is its footprint, which counts every page it touches. Frameworks, the shared library cache and compressed pages are touched by many processes at once, so a page three apps share is counted three times. A Mac shares a great deal, and the sum drifts further from reality the more apps are open.

What an app touches, and what comes back

You can ask how much memory an app touches, or how much comes back when it quits. The first is its footprint. The second is how much the machine's own used figure drops from before to after, and it's smaller, because the pages the app shared with something else stay where they are.

Both numbers are fine on their own. The trouble starts when one sentence mixes them, and that's what a menu bar does when it lists apps by footprint and then offers to free their sum. For one set of apps on this machine, our own menu bar said 10.66 GB releasable when the honest figure was 8.5 GB.

Now every screen that promises memory uses the second number. The scaling is worked out once, over every app Mint tracks, rather than over whatever subset a screen happens to list. If it weren't, the Memory tab and the menu bar could give different answers for the same app on the same machine. A pile header is also the sum of the rows drawn under it. That sounds obvious, and it wasn't true: an 8 GB header sat over rows that added up to 14.

The figure is never scaled up. What the machine reports is the ceiling, and attribution only moves a number down toward it.

Why the promise and the receipt disagree

With the units matching, you can hold what a screen promised up against what happened. We released the same 2.1 GB process three times and measured both.

What the screen promised, what the machine gave backThree real releases of the same 2.1 GB process, in gigabytes.PromisedMeasured afterwardsRelease 11.732.15Release 21.731.39Release 31.871.99The receipt and the next reading of the machine agreed within 1 to 3% every time: 1.391 against 1.406, and 1.989 against 1.950.
These are not supposed to be equal. A promise is one app’s share under the average sharing on this machine; a receipt is what came back when that particular app quit. What they have to share is the unit.

They miss in both directions, and that's expected. The promise is the app's share at this machine's average level of sharing. The receipt is what came back when that particular app quit. An app with mostly private memory beats its promise, and one that shared a lot falls short. Before the units were fixed, a promise could be larger than all the memory the machine was using, and putting the pair side by side makes that kind of error easy to see.

The receipt does match the machine's next reading: 1.391 against 1.406, and 1.989 against 1.950. What the receipt says is what you'll see a second later.

What "idle" used to mean

The other half of this is which rows arrive already ticked. The old rule wanted a large app, an ordinary one, safe to quit politely, and idle. Idle was judged by the last time the app was in front.

Which rows arrive already tickedThe same three rows under the old rule and the one that replaced it.Old ruleNew ruleA terminal, last clicked two hours agoAn agent has been working inside it the whole timeA browser at 2 GB, genuinely unusedNothing running, nothing hostedAn app at 34% of a coreIn the background and working hardThe middle row is why the fix could not be to stop pre-ticking anything. That row is the reason the feature exists.
The old rule asked when you last clicked something. Two of these three rows are busy while nobody is looking at them, and one of them was scored as the most dormant thing on the machine.

So a terminal nobody had clicked for two hours, with an agent working inside it the whole time, scored as the most dormant thing on the machine. Claude Desktop's hosted sessions and an editor's agent workers got pre-ticked the same way. Apple's own apps were never candidates, for unrelated reasons, so Terminal was safe by accident and every third-party terminal wasn't.

The obvious fix was to stop pre-ticking anything, and we didn't take it. A browser holding 2 GB that you aren't using is the whole reason the feature exists. What had to change was the question the rule asked.

The new rule asks about work, using signals that were already being collected. It looks for a hosted agent in the row or in anything the row started. Then it checks for CPU activity in the row and everything under it over the sample window. Last, it checks for a program running under a shell inside the app, which is how a terminal, or an editor's built-in terminal, looks from outside.

A row with work behind it is still listed, in the in-use pile, with the reason written on it, and it stays unticked. It might say "running Claude Code" or "busy · 34% CPU", and it waits for you to decide.

Getting the CPU part right cost us an afternoon. Process CPU time comes back in Mach ticks, and on Apple Silicon the conversion isn't one to one: a busy loop we timed at 125.7 ms reported 3,016,253 ticks. You have to get the ratio from the system, not from arithmetic that happens to look right on one machine.

What we got wrong, and how it announced itself

Our demo fixture described a background helper with a process id of 0.

The release path checked that the process was alive, then sent it a termination signal. On Unix, a signal sent to process 0 goes to the caller's whole process group. Mint killed itself in 0.66 seconds and took down the shell script that had started it. We measured this on a copy built for that kind of probing, so nobody's real session was involved.

The fix is three lines, and working out where they belonged took longer than writing them. A number that comes from a fixture is still a number the executor acts on, so the check has to sit where the action happens, not where the fixture was written. A row naming a process id of zero or below is now treated as changed and dropped, the signal loop skips it, and a test checks for it at that spot. The fixture keeps its zero on purpose, so every future copy runs into the guard.

Agents look idle from outside

The terminal in that figure is what a developer's machine looks like, and more of them look like it every month. An agent session can be a program under a shell inside a terminal, a helper an editor started, or a desktop app hosting a coding session. From outside, each of these looks like an app nobody has clicked in a while.

A memory tool has to get this case right before it's safe to leave running, and that's why the rule reads work instead of attention. Mint lists those rows, says what they're doing and leaves them unticked. You can still quit one with a click if you want to. It won't happen just because you stopped looking at the window.

What we took from this

The arithmetic was right all along. Both numbers we started with were correct measurements of different things, and the damage came from a sentence that put one where the other belonged. Once a promise and a receipt use the same unit, you can check one against the other afterwards instead of taking the tool's word for it.

Idle is a claim about what a machine is doing, so it should be measured on the machine. Attention was the easy signal to read, and it answered a different question. The work signals took more code, though the data behind them was already being collected for something else.