Keeping a Mac Awake Without Breaking Sleep: IOKit Power Assertions vs pmset
Why 'sudo pmset disablesleep' is a trap for a shippable app, and how a single IOKit power assertion keeps a Mac awake with zero privileges and automatic cleanup on crash.
- Swift
- macOS
- IOKit
The naive way, and why it’s dangerous
I built ChargeBeep, a tiny menu-bar app that keeps a Mac awake while it charges and chimes when it hits a target percentage. The single hardest design decision in the whole app was also the least glamorous: how do you stop a Mac from sleeping?
The first answer everyone reaches for is pmset:
sudo pmset disablesleep 1 # ... and later ...
sudo pmset disablesleep 0
This works, and it’s a trap. Two problems, one fatal:
- It needs root. Every invocation wants a password, which is a miserable UX for an app that’s supposed to be invisible.
- It’s global mutable system state with no owner. If your app sets
disablesleep 1and then crashes — or gets force-quit, or the machine loses power mid-run — before it can set it back to0, the user’s Mac now never sleeps, forever, with no visible cause. You’ve silently broken their computer and there’s nothing pointing back at you.
Shipping something that can leave the OS in a broken state if your process dies is
not acceptable for a paid app. So pmset was out for the default behavior.
The right tool: a power assertion
macOS has a purpose-built API for exactly “please don’t idle-sleep while I’m doing something”: IOKit power assertions.
import IOKit.pwr_mgt
var assertionID: IOPMAssertionID = 0
func keepAwake(_ on: Bool) {
if on {
let name = "ChargeBeep keeps the Mac awake while charging" as CFString
IOPMAssertionCreateWithName(
kIOPMAssertionTypePreventUserIdleSystemSleep as CFString,
IOPMAssertionLevel(kIOPMAssertionLevelOn),
name, &assertionID)
} else if assertionID != 0 {
IOPMAssertionRelease(assertionID)
assertionID = 0
}
}
Three things make this the correct choice:
- No privileges. No password, no
sudo, nothing. Any app can create an assertion for its own process. - It’s owned by your process. The moment your process exits — cleanly, by crash,
by
kill -9— the kernel releases every assertion it held. Sleep returns to normal automatically. There is no “stuck” state to leak. - It’s honest. The human-readable name you pass shows up in
pmset -g assertions, so a curious user can see exactly who’s keeping their Mac awake and why.
The named-assertion string isn’t decoration — it’s the accountability. “Anonymous process prevents sleep” is a support ticket; “ChargeBeep keeps the Mac awake while charging” is self-documenting.
When you genuinely need pmset anyway
A power assertion of type PreventUserIdleSystemSleep keeps the Mac awake with the
lid open. It does not keep it awake with the lid closed — that’s clamshell
sleep, a different mechanism, and the only way to override it really is
pmset disablesleep.
ChargeBeep exposes that as an opt-in “powerful mode.” The trick to making it tolerable is to not prompt for a password every time. Instead, on enabling the mode once, it installs a single narrowly-scoped sudoers rule:
username ALL=(root) NOPASSWD: /usr/bin/pmset disablesleep *
written to /etc/sudoers.d/chargebeep via one admin prompt. After that, the app can
toggle only that one command without a password — not a blanket NOPASSWD, just
pmset disablesleep. Disabling the mode or uninstalling removes the file and restores
sleep. It’s the difference between “trust me with root forever” and “let me run
exactly this one command.”
The takeaway
When you need the OS to behave differently, look for the API that scopes the change to
your process’s lifetime before you reach for the global flag. pmset disablesleep and
IOPMAssertionCreateWithName do almost the same thing — but only one of them cleans up
after you when everything goes wrong, and “when everything goes wrong” is exactly when
it matters.