caffeinate keeps your Mac awake until you close the lid. Then it sleeps anyway, because
lid-close sleep is a separate setting that lives behind sudo. SleepSwitch flips both at
once, from a single menu bar icon.
Useful when you want to close the lid and keep a build, a download, a render, or an SSH session running — without an external display, without a dummy HDMI plug, without leaving the lid propped open.
- Lid-close sleep off. Shut the laptop, it keeps running.
- Idle sleep off. Your idle timer is ignored while the mode is on.
- Screen stays lit while the mode is on — no separate switch to think about.
- Ask for the password once. The installer sets up a narrowly scoped
sudorule, then toggling never prompts again. - Won't flatten your battery. The mode switches itself off below a charge you pick, and optionally the moment you unplug.
- Tells you it worked. A quiet tone as the lid closes and another as it opens.
- Nothing left behind. Quit it, force quit it, or crash the machine — the sleep ban is cleared either way.
- Updates itself from GitHub — checks daily, hands you the installer, never swaps binaries behind your back.
- English and Russian, app and installer, following your system language.
- Plain Swift, no dependencies, no background daemon, no telemetry.
- Universal binary, Apple Silicon and Intel.
Grab the .pkg from the latest release
and open it.
Note
The app is not signed with an Apple Developer certificate, so macOS blocks the first
open. On macOS 15 Sequoia and newer, Control-click no longer overrides that: open the
.pkg, let it be refused, then go to System Settings → Privacy & Security, where an
Open Anyway button now sits under Security. On macOS 14 and earlier, Control-click
the .pkg → Open → Open again. One-time either way.
The installer drops the app in /Applications, launches it, and — unless you untick the
box under Customize — installs the passwordless sudo rule.
| Action | Result |
|---|---|
| Left click the icon | Toggle the mode |
| Right click | Menu: launch behaviour, login item, updates, sudo rule |
The icon is the state:
| Icon | Meaning |
|---|---|
| 🌙 | Normal — your Mac sleeps as configured |
| ☕️ | Mode on — sleep fully blocked |
| Partial — idle sleep blocked, but the lid still puts it to sleep |
Two independent layers, because macOS treats these as two different things:
| Layer | Blocks | Needs root |
|---|---|---|
pmset -a disablesleep 1 |
Lid-close sleep, and all sleep | Yes |
PreventUserIdleSystemSleep assertion |
Idle sleep | No |
PreventUserIdleDisplaySleep assertion |
Display turning off | No |
The IOKit assertions are held by the process, so they evaporate the moment the app dies —
they can never get stuck. The pmset setting persists, so the app clears it on quit and on
SIGTERM, and reports the real state at launch by reading SleepDisabled straight from
IOPMrootDomain. The failures that reach no handler at all are covered separately, in
When the app dies holding the ban.
pmset disablesleep needs root. Asking for a password on every toggle is unusable, so the
installer writes /etc/sudoers.d/sleepswitch:
you ALL=(root) NOPASSWD: /usr/bin/pmset -a disablesleep 0
you ALL=(root) NOPASSWD: /usr/bin/pmset -a disablesleep 1
Two commands, no wildcards. sudo matches arguments exactly, so this grants the ability to
toggle a sleep setting and nothing else — pmset -a sleep 0 still asks for a password.
The rule is written, validated with visudo, and installed entirely as root inside a
mode-0700 temp directory, so there is no user-writable file to swap in between the check
and the install. The username is validated before it reaches sudoers, and the rule is only
granted to an account in the admin group.
Opt out at install time, or later from the menu — or by hand:
sudo rm /etc/sudoers.d/sleepswitchWith the mode on, closing the lid plays a muted porcelain tone and opening it plays a higher one — enough to confirm the Mac stayed awake without opening anything to check. The menu switches them off, and they never sound with the mode off, since closing the lid then simply means sleep.
The tones are synthesised at build time by Tools/make-sounds.swift,
so no audio file lives in this repository. A Mac without a lid gets neither the cues nor the
setting.
The IOKit assertions cannot get stuck — they belong to the process and go when it goes. The
pmset setting is different: it outlives SIGKILL, a Force Quit and a panic, none of which
reach any handler the app could install. Left alone, that means a Mac that will not sleep,
with no icon in the menu bar to say why and no battery guard either, since the guard ran in
the process that just died.
So a launch agent reconciles it at login and once a minute. While the ban belongs to the app, the app renews a lease file; a stale lease means it died holding the ban and the agent clears it. A missing lease means the ban was never the app's — armed by hand in a terminal, perhaps — and the agent leaves it strictly alone.
The lease is written before the ban is armed. Recorded afterwards, a kill landing between the two would leave a ban with no lease, and the agent would read that as somebody else's — the one reading that must never apply to a crash of ours. The cost is a claim that can briefly exist with no ban behind it, so the app drops it as soon as the ban turns out not to be there, and the agent removes a stale claim standing alone. Only stale ones, so a claim written a moment ago and not yet armed is never pulled out from under a live app.
It runs as you rather than as root, and clears the ban through the same narrow sudo rule
the app uses, so it grants itself nothing. Without that rule it has no quiet way to act
either — one more reason the installer sets the rule up by default.
Keeping a Mac awake makes one mistake easy: switch the mode on, close the lid, drop the laptop in a bag, and it runs hot in there until the battery is flat. So the mode switches itself off once the battery falls to a threshold you choose — 20% out of the box — and, if you ask it to, the moment the power adapter is unplugged. Both settings sit in the menu, and a notification tells you which of the two fired.
On a Mac with no built-in battery the two settings are not shown at all — an inert control is worse than no control. The check is by power-source type rather than by "is there a power source", since a UPS plugged into a desktop is one too. And if the battery reading is ever missing, nothing trips: switching the mode off for a reason the app cannot state would be worse than leaving it be.
The app asks GitHub once a day whether a newer release exists. If one does, a system notification offers to download it or to skip that version for good — nothing pops a modal dialog over whatever you were doing. A check you start yourself from the menu answers in a window instead, since you are standing there waiting for it. Turn the check off from the menu and nothing goes over the network unless you ask.
SleepSwitch does not replace itself. It downloads the .pkg from the release and opens it
with the system Installer, so the upgrade goes through the same authenticated flow as the
first install. Requests are pinned to https on GitHub hosts, redirects included — anything
else is refused.
Sleep and screen lock are separate macOS settings, and SleepSwitch only touches sleep. If your Mac asks for a password when you reopen the lid, that is the lock, and macOS deliberately requires your account password to change it — no app can flip it silently.
sysadminctl -screenLock off -password - # disable
sysadminctl -screenLock immediate -password - # restore
sysadminctl -screenLock status # checkThe menu has a shortcut to the matching System Settings pane.
./make-installer.sh # → dist/SleepSwitch-<version>.pkg
./install.sh # or straight into /Applications, no installer
./Tools/run-tests.sh # tests; add --network to hit the real GitHub APINote
A local build leaves build/SleepSwitch.app, and Spotlight indexes it — so the app
shows up twice in search next to the installed copy. rm -rf build dist clears it.
.metadata_never_index does not help here; Spotlight ignores it for ordinary
subfolders.
Xcode or the Command Line Tools is the only requirement. The app icon is drawn in code by
Tools/make-icon.swift at build time — no binary assets in the
repo. A missing translation fails the build:
Tools/check-localization.sh diffs the L("…") keys in the
sources against every Localizable.strings.
Releases are cut by tag:
git tag v1.0.1 && git push origin v1.0.1macOS 13 Ventura or newer. Apple Silicon and Intel. The interface follows your system language — English or Russian.
To upgrade, delete nothing. Open the newer .pkg — the installer stops the running
copy and replaces the bundle in place. Your settings and the sudo rule survive.
To remove it for good, use Uninstall SleepSwitch… in the menu. It clears the sleep
ban first — while the sudo rule is still there to do that quietly — then removes the app,
the rule, the receipt and your settings, and quits. One password, one confirmation.
From a checkout you can run uninstall.sh instead, or do it by hand:
sudo pmset -a disablesleep 0 # first — see the warning below
sudo rm -rf /Applications/SleepSwitch.app
sudo rm -f /etc/sudoers.d/sleepswitch
sudo pkgutil --forget com.ganin.sleepswitch.app
defaults delete com.ganin.sleepswitchWarning
Clear the sleep ban before deleting the app. It is a system setting that outlives the process, so dragging the app to the Trash while the mode is on leaves your Mac unable to sleep with no switch left to turn it off. Toggling the mode off in the menu first does the same thing.
Dropping the app in the Trash is enough only when the mode is off and you do not mind the
sudo rule and the receipt staying behind.