coffee-bar holds your Mac awake while a coding agent is working, and lets it sleep the moment every agent is waiting on you. This page defines all three controls — Serving in the panel, Display and Battery floor in the Preferences window — states exactly what coffee-bar asks macOS for, and says when it refuses to hold at all.
Three positions, and they rank differently. The ranking is the whole policy.
Off never holds. It is absolute, and an active agent session does not override it. coffee-bar overrides your Mac's own sleep policy, so a product that ignored its own off position because some background session disagreed would be a trust failure rather than a convenience.
Auto is the default, and the sessions decide. coffee-bar holds the Mac awake while at least one session is starting or working, and it releases the hold when every session is done or waiting on you.
This is what the product is for. The two working states are the same two the panel counts, so the line that says how many sessions are working and the hold itself can never disagree.
On always holds, even when no session is running. An explicit request outranks a quiet session list, so a hold you asked for does not end the moment the last session goes idle. The battery floor below still applies to it.
Two positions. This control answers a different question from Serving: that one says whether to hold at all, this one says what a hold covers.
Sleeps is the default. coffee-bar holds the machine awake and your screen still goes dark.
That default is the product's central difference from caffeinate -d.
An agent can work through the night without the display burning power all night
with it.
Stays on adds the display assertion. It rides the system hold and never outlives it, so the Off position and the battery floor govern your screen exactly as they govern the machine.
Some work is worth watching, so this is a setting rather than a promise. It is also the reason the two are separate controls: asking for the screen is not the same as asking coffee-bar to hold unconditionally.
Nine positions, and a third question. Serving says whether to hold at all, Display says what a hold covers, and this one says how much battery a hold may spend.
The Battery floor control ships at 15%, and that is a default rather than a fixed limit. The Preferences window lets you move it, and your choice is remembered across launches.
The control offers every step of 5 from 10% to
50%. Those are also the ends of what the app accepts: it brings a
floor outside 10 to 50 inside that range before the
decision reads it.
On battery, at or below 15%, coffee-bar does not hold. That is where the floor ships, and it outranks an explicit On, because the floor is a safety limit on the machine rather than a veto on one control position.
At or below is exact. The decision suppresses the hold when the charge is less than or equal to the floor, so at the floor itself coffee-bar does not hold — at 15% on a fresh install. The floor outranks the Display control as well, and withdraws the display assertion at the same moment, because a screen held through the floor drains the battery faster than the hold the floor has just refused.
The panel names the reading the decision was made on. When a hold you asked for was refused outright, or was honoured and then released as the battery drained, it also names the position your control landed on — and it words those two cases differently, because a click that worked for hours was not a refusal.
A Mac on mains power has no floor, and a machine with no battery at all never reaches this rule.
coffee-bar asks macOS for three IOKit power assertions and nothing else. It holds PreventUserIdleSystemSleep and NetworkClientActive whenever it is serving, and PreventUserIdleDisplaySleep beside them only under Stays on.
All three are named, so pmset -g assertions tells you who is keeping
your Mac awake:
PreventUserIdleSystemSleep, named coffee-bar is serving.NetworkClientActive, named coffee-bar is keeping the network up. It is a request and not a promise: macOS may decline it on battery or under thermal pressure.PreventUserIdleDisplaySleep, named coffee-bar is keeping the display awake. It carries a different sentence on purpose, so that each line reads as its own thing rather than as a duplicate of another.These are the same mechanism caffeinate uses. The app needs no root, no password and no kernel extension, and it writes nothing to your power
settings. Releasing an assertion restores your normal behaviour exactly.
Everything above keeps the Mac awake with the lid open. Closing the lid still puts it to sleep, because an assertion does not survive that — the lid is a different mechanism, and overriding it means changing a system setting rather than asking for a hold.
That is what lid-closed mode does, and it is the one part of the product that involves root at all. coffee-bar's everyday work needs no root and no password, and lid-closed mode is the one opt-in extra where macOS runs a small coffee-bar helper as root after you approve it in System Settings, still with no password.
That distinction is worth being exact about, because it is the whole posture. The app process never elevates: it takes no credentials, runs no interpreter as root, and has no route to raising its own privilege. What it can do is ask macOS to install a separate, very small helper — and it is macOS that installs that helper, runs it, and refuses to run it until you have said yes yourself.
On a build signed by coffee-bar's developer — the notarised disk image — the Preferences window carries an Arm lid-closed mode button under Power, beside the hold length. Clicking it asks macOS to register the helper. That is the whole flow, and there is no command to type.
Nothing appears on screen asking you to approve it. macOS files the helper away switched off, and the switch is one you have to go and find: open System Settings, then General, then Login Items & Extensions, and turn coffee-bar on there. No dialog, no authorization sheet, and no password — not at the click, not at the approval, not afterwards. A window waiting for a prompt is waiting for one nothing will send, and until that switch is on the helper is installed and inert.
Once it is on, the helper does what the command route does: it records what it is about to change, holds the setting, and puts it back when the time is up. The window reports the hold the helper recorded rather than the number the slider showed, so what you are told is what was actually taken.
coffee-bar tells you lid-closed mode is armed at the moment you arm it from the Preferences window, and it cannot answer the question later, because the journal that records the hold belongs to root and this app runs as you.
The second half is measured rather than assumed. The journal is a root-owned file inside a root-owned directory this app has no execute permission on, so it cannot read the file, read its permissions, or learn whether it exists at all. Loosening those permissions to give the window a live reading was considered and rejected: they are the security property that makes a root-owned file safe to hand to a root process, and they outrank a nicer window.
Nor is there a second route to the answer, and that is a design decision
rather than an oversight. The channel between the app and the helper is
deliberately tiny: it carries two messages, arm and
revert, and there is no third that asks whether a hold is still in
force. So even a signed build with its own helper running cannot poll it. The
app is told the hold at the moment it is granted, and nothing after that reports
a change.
So the answer at any later moment — after a restart, or on a Mac you did not
arm yourself — comes from
sudo /Library/PrivilegedHelperTools/coffee-bar-probe report, which
prints what is armed, since when, and until when.
The same window takes it away again. Remove the helper ends the hold first and unregisters second, and that order is the point: unregistering a helper that is still holding a setting would leave the setting changed with nothing left to put it back. If the hold cannot be ended, the removal stops and says so rather than carrying on and hoping.
An armed hold lasts 8 hours, and then the setting goes back. The Preferences
window lets you choose a different length and carries your choice into whichever
route you arm by; on the command route that same choice is --ttl,
which takes a number of seconds. Either way the ceiling is 24 hours. Nothing
here watches your work, so the hold cannot end early when the agent finishes,
and the time you ask for is what a machine you have walked away from will
spend.
That is deliberately long, and the reason is what actually ends a hold on a laptop. The watchdog checks the battery before it checks the clock: on battery the hold ends at 15% however much time is left, so a machine shut in a bag is bounded by charge rather than by the number above. On mains power there is nothing to protect, and a short hold there only ended the long unattended run this mode exists for.
The display is forced off rather than left to sleep on its own. A closed lid over a lit panel is heat with nowhere to go, and it burns the battery this whole feature exists to save.
A build coffee-bar's developer did not sign cannot register a helper, and a Homebrew install is unsigned by design — the formula compiles the source on your own machine, so the bundle carries no team identifier and macOS has nothing to pin a helper to. On those builds the button is disabled, and the window says why and names the command instead.
That route is not deprecated, it is unchanged, and it arms the same mode.
It is two commands, because you cannot arm the probe where it
ships. Arming installs a launchd job that root runs and keeps running,
so the probe refuses unless its own file is owned by root and sits in
directories nobody else can write. No install location qualifies: macOS ships
/Applications writable by every administrator account, and a
Homebrew prefix, a build tree and a copy on your Desktop all belong to you. In
any of them another process could swap the binary that root is about to run, and
that is the refusal doing its job rather than a fault.
So the first command copies the probe to
/Library/PrivilegedHelperTools, which macOS keeps root-owned for
binaries a root process runs; the second arms that copy. Only the source path
depends on where you installed coffee-bar, and the Preferences window prints the
pair with the path this copy actually has — it asks the running app where it is,
which this page cannot do. Prefer the window's version if the two disagree.
sudo install -o root -g wheel -m 755 /Applications/CoffeeBar.app/Contents/MacOS/coffee-bar-probe /Library/PrivilegedHelperTools/coffee-bar-probe
puts the copy in place, owned by root and writable by nobody else.
install rather than cp because it sets the ownership in
the same call that writes the file, so the copy is never briefly there at yours.
Run it again after you upgrade coffee-bar: replacing the app leaves the old copy
behind, and it is the copy that gets armed.
sudo /Library/PrivilegedHelperTools/coffee-bar-probe arm records what it is about to change,
installs a launchd watchdog, sets the system to stay awake with the lid shut,
and forces the display off. The order matters: nothing is changed until the
record is on disk and the watchdog that undoes it is loaded, so a process killed
half way through leaves no held setting behind it.
sudo /Library/PrivilegedHelperTools/coffee-bar-probe revert restores the sleep setting you had
and removes the watchdog. You do not need it for an ordinary hold, which ends on
its own.
revert undoes the hold, but it does not remove the copy of the
probe you installed by hand. That file is root-owned and stays where you put it
until you delete it with
sudo rm /Library/PrivilegedHelperTools/coffee-bar-probe. Run
revert first: deleting the probe while a hold is armed leaves the
sleep setting changed with nothing left to put it back.
Waiting on you is the panel heading for the sessions that are blocked on a human. Under Auto these sessions do not hold the Mac awake, and that release is the entire product.
Two session states reach that heading: awaitingPermission, where
the agent is asking you to approve a tool, and awaitingInput, where
the agent is asking you a question. A session that waits on you holds
nothing.
The panel carries a second line beside it, counting the sessions that are actually holding the Mac awake — the ones that are starting or working. The two lines answer different questions and are true independently, which is the only way two lines like these can sit together and stay honest.
Five Claude Code hooks, and nothing else. The Install page has the block to paste, and until those entries exist the app runs and no session event ever reaches it.
coffee-bar reads hook events to learn whether an agent is working, and it does not read your conversations.
Two fields in those payloads carry conversation content, and coffee-bar
declares no property for either one. transcript_path and
last_assistant_message reach no stored property, no log line and no
part of the screen: they are discarded where the payload is decoded, and tests
hold that boundary against recorded payloads that really carry both fields.
coffee-bar reports what it can see, and says so when it cannot see something. The panel tells you which hooks are missing from your settings file, and tells you separately when this copy of the app is not listening on its socket. A settings file that looks correct proves nothing about events arriving, so those two answers never merge into one.