Making a MacBook That Never Sleeps#
2026-11-01

Taking It for Granted That Work Stops When I Move#
When I run a long job, I plug in, leave the lid open, and keep caffeinate running in a terminal. It’s a built-in macOS command that stops the Mac from falling asleep and cutting the job off. That was as far as I took it. When I had to move, I closed the lid and simply accepted that the work would pause in the meantime.
In my previous essay, about watching a coding agent session on my laptop from my phone, I wrote one line in passing: “Close the lid and not even caffeinate can stop it.” But once I could watch the session from my phone, a question came up. If I can carry the screen around, why can’t the laptop keep working with its lid closed, in my bag, while I move?
It can. It takes one command. The hard part came after that. Keeping a Mac awake takes one line, but making it safe to leave that on and forget about it took design. This essay follows that one line all the way to a small tool I built and published.
What caffeinate Blocks and What It Doesn’t#
First I checked how far caffeinate actually goes. I use two of its options:
-i: prevents idle sleep, where the Mac falls asleep on its own because nobody is touching it.-s: prevents system sleep. According to the man page, it only applies while the Mac is on AC power.
caffeinate works by placing a “don’t sleep” request (an assertion) with macOS power management. Apple’s IOKit power management documentation is explicit about the request -i places: even while it’s held, the system may still sleep when you close the lid, choose Sleep from the Apple menu, or run low on battery. For the request -s places, the same documentation says nothing about what happens when the lid closes. I decided not to rely on behavior the documentation doesn’t promise.
In short, caffeinate keeps the Mac from sleeping when it’s left alone. It doesn’t keep it awake when someone puts it to sleep. If you choose Sleep yourself, the Mac really does sleep, and whatever was running on it stops too. Closing the lid is no different.
disablesleep: It Beats the Lid, but the Setting Stays#
The way to stay awake with the lid closed turned out to be in pmset, the command that changes macOS power management settings.
sudo pmset -a disablesleep 1 # disable sleep
sudo pmset -a disablesleep 0 # back to normalWith this on, the Mac stays awake when you close the lid. What surprised me the first time was that the display stays on too. So you need to turn the display off with pmset displaysleepnow first, then close the lid.
The bigger difference lies elsewhere. caffeinate is a request held by a running process, so it disappears when the process exits. disablesleep, on the other hand, changes a system setting. The value is stored in the power management preferences file (/Library/Preferences/com.apple.PowerManagement.plist), so it survives after the command ends and even after a reboot.
| caffeinate | disablesleep | |
|---|---|---|
| When the lid closes | Sleeps | Stays awake |
| Turns off | When the process exits | Only when you turn it off |
| After a reboot | Gone | Still on |
| Admin rights (sudo) | Not needed | Needed |
Forgetting to turn off caffeinate costs almost nothing. Forget to turn off disablesleep, and days later the Mac still won’t sleep, even in your bag. So the two aren’t alternatives so much as tools for different situations: caffeinate when the lid can stay open, disablesleep only when it has to close.
Making It Safe to Forget#
At first I wrote two shell functions to turn it on and off (awake and unawake). But having a function to turn it off doesn’t stop you from forgetting to. So I wrote down three requirements:
- While it’s on, it should always be visible in the menu bar, with a way to turn it off right there.
- It should turn itself off after a maximum time.
- While it’s off, nothing should be watching for it unnecessarily.
Once written down, requirements 1 and 3 collided. To show an indicator only while it’s on, something has to know whether it’s on, and to know that, something has to check periodically. That checking is exactly the watching I wanted to avoid.
The fix was to change who does the checking. Instead of a menu bar app that’s always running, awake, which turns the setting on, launches the app itself. The app checks the state every few seconds only while the setting is on, showing the time left, and quits on its own once the setting is off. While it’s off, nothing is running at all. Requirement 2, the auto-off, is handled by a timer I’ll come back to.
One gap remained. If the Mac is force-restarted or rebooted while the timer is running, the timer is gone and only the setting is left. For that case, the app is launched exactly once at login. If the setting is off, it quits immediately; if it’s on, it puts ☕️ in the menu bar. A setting left behind without a timer mostly happens after a reboot, so instead of watching constantly, it checks once at that moment.
Install or Build?#
SwiftBar is an app that puts the output of shell scripts in the menu bar. I planned to use it at first, and before installing it I checked whether it was safe.
- I downloaded the latest version (2.1.1) from the official GitHub releases and inspected its signature. It was signed with an Apple-issued developer certificate and carried Apple notarization (a mark that it passed Apple’s malware check).
- It’s MIT-licensed open source with about 4,700 GitHub stars as of 2026-11-01, and has been installed more than 30,000 times through Homebrew, the macOS package manager, over the past year. The repository had no published security advisories.
It was a perfectly good app. But when I counted again, what I needed was one indicator and one menu item. For that, I judged it better to keep code I fully understand than to bring in an entire app someone else wrote. It also mattered that, working with a coding agent, writing that much code took about as much effort as installing and configuring an app. In the end, the menu bar app came to 100 lines, and about 200 including the scripts.
Scale is the deciding factor. If the things you want in the menu bar grow to several, like VPN status and build status, SwiftBar, where you just add another script, is far better. It’s a conclusion similar to my PR agent essay, where I borrowed the design instead of buying the product, but this time the reason was simpler. What I needed was small.
When an Old Timer Turns Off a New Setting#
I also had to decide where the auto-off timer would run. One option is for awake to stay running in the terminal and turn the setting off when time runs out or you press Ctrl+C. The other is for awake to return immediately while the timer runs in the background. Since the menu bar already shows the time left and an off button, there was no reason to tie up a terminal tab, so I went with the background timer.
Either way, there was the same trap. If you run unawake from another terminal, the setting turns off, but the existing timer keeps running. Run awake again after that, and when the old timer’s time comes, it turns off the setting you just turned on. If you were working with the lid closed, the Mac goes to sleep earlier than you expected.
The fix was to record only one “current timer.” A state file holds the current timer’s process ID (PID) and its deadline. Turning it on or off first deletes that record, then kills the old timer. A timer whose time is up turns the setting off only if it is still the current timer.
STATE=/var/run/sleepguard.state # one line: "<timer PID> <deadline>"
stop_timer() {
[ -f "$STATE" ] || return 0
pid=$(cut -d' ' -f1 "$STATE")
rm -f "$STATE" # delete the record first,
kill "$pid" 2>/dev/null # then kill the old timer
}
# Timer side: when time is up, turn off only if I'm still the current timer
if [ "$(cut -d' ' -f1 "$STATE" 2>/dev/null)" = "$$" ]; then
rm -f "$STATE"
pmset -a disablesleep 0
fiThe real code also checks that the PID really belongs to a timer process before killing it, since a process ID can be reused by another program.
Building and Verifying#
I first wrote the menu bar app in Swift. But inside Command Line Tools (the developer tool bundle you can install without Xcode), the Swift compiler and the SDK (the libraries an app uses to call macOS features) were off by a minor version, and the build failed. Instead of reinstalling the tools, I rewrote the same thing in Objective-C, which built without trouble using the compiler from the same bundle. For an app this small, switching languages cost less than fixing the toolchain.
Verification was split between me and the agent. The coding agent I worked with (Claude Code) can’t type a sudo password, so it couldn’t change the real setting. Instead, it built a fake pmset that only logs its calls and used it to test the timer logic: whether time running out turns the setting off, whether turning it on again cleans up the old timer, whether it ever kills an unrelated process. It also launched the menu bar app against a fake state and took a screenshot to check the indicator. I checked the real thing myself: entering my password, seeing ☕️ appear, turning it off from the menu, and watching it switch off by itself after the set time.
Repeatable logic checks went to the agent; the final check that needed privileges went to me. The fake-pmset tests became the repository’s test suite as they were.
From My Own Script to a Tool for Others#
Once it worked, I wanted to share it. But looking at it again with publishing in mind, problems surfaced that I hadn’t noticed while using it alone. Using it myself, the bar was “is it safe if I forget?” Publishing it, the bar became “is it safe for someone who doesn’t know how it works?”
- A personal path: The file that launches the app at login had my home folder path written into it. I changed the install script to generate it with the installing user’s path.
- Where the root script lives: The script that runs as root (the system’s superuser account) first lived under my home folder, where my own account could edit it. If a file run with sudo can be edited by a normal account, malware that takes over that account can swap the file and gain admin rights. I moved it under
/usr/local/libexec, which only root can modify, and made the installer check that nobody else can write to that path. - The uninstall script: Uninstalling kept going even if the password prompt failed. That left the setting on while removing the menu bar indicator and the command to turn it off. The uninstaller was creating the exact “on, but nobody knows” state the tool exists to prevent. I changed it to stop without removing anything when that happens.
- An unrecognizable name: In System Settings, under “Allow in the Background,” the login item showed up only as
open, because I had launched the app with theopencommand. I changed it to run the app’s executable directly. - A claim stronger than its evidence: The README said “
caffeinatecan’t survive a closed lid” without distinguishing options, but only-iis backed by documentation. I changed-sto “not documented.”
I published the result as sleep-guard-for-mac. Installing it gives you three commands:
git clone https://github.com/tedkim81/sleep-guard-for-mac.git
cd sleep-guard-for-mac && ./install.sh
awake # keep awake for 3 hours and turn the display off (or: awake 90m, awake 2h)
unawake # turn it off
screenoff # turn only the display off (handy with caffeinate)Keeping Work Going on the Move#
Staying awake wasn’t enough. For work to really continue while moving, two more conditions matter.
Network#
The moment you leave the office Wi-Fi, the laptop loses its connection. A coding agent calls a model API constantly, and to watch the session from your phone with Remote Control, as in my previous essay, the laptop has to stay online too. The easiest answer is your phone’s Personal Hotspot.
According to Apple’s support article, starting with macOS Tahoe 26 you can set “Ask to join hotspots” in Wi-Fi settings to “Automatic,” and the Mac will join a nearby iPhone’s hotspot whenever Wi-Fi isn’t available. Earlier versions don’t have the “Automatic” option. Either way, connecting to the hotspot before you leave is the reliable choice, because the moment the network switches mid-trip, in-flight requests and long-lived connections like SSH (remote server logins) can drop. For remote sessions that need to stay connected for a long time, use something like tmux or mosh that can reattach after a drop. Keep an eye on your phone’s data usage and battery as well.
Heat#
Can you put an awake laptop with its lid closed in your bag? At first I thought it would be fine as long as the vents weren’t blocked. I couldn’t find official Apple documentation of the MacBook Pro’s airflow, but from what’s known, it pulls air in along the bottom edges of the sides and pushes it out the back near the hinge.
Clear vents aren’t enough, though. Inside a closed bag, the laptop pulls back in the hot air it just pushed out, so the air it’s cooling with keeps getting warmer. The heat that would normally escape through the aluminum body is trapped by the fabric too. Fortunately, macOS lowers performance as temperatures rise to protect the hardware, so the real cost is closer to slower work, a hot laptop, and shorter battery life than to damage.
So the deciding factor is workload. For light work like downloads, keeping an SSH session alive, or an agent waiting on API responses, putting it in your bag for a short trip is usually fine. For heavy work like builds, running local models, or encoding video, carry it out of the bag. If you’re unsure, take it out after 10 to 20 minutes and touch the bottom. If it’s hot, that job shouldn’t run in your bag.
Closing#
Here’s how I split things up now:
| Situation | What I use |
|---|---|
| Long job with the lid open | caffeinate -i <command> (released when the job ends) |
| Display needs to stay on too | caffeinate -d |
| Moving with the lid closed | Connect to a hotspot before leaving, then awake |
Looking back, the effort in this project went into turning things back off, not turning them on: the auto-off timer, the indicator that only appears while it’s on, the one check after a reboot, cleaning up old timers, and an uninstaller that stops when it fails. If you build a tool that changes a system setting, it’s worth counting the ways that setting gets turned back off before you count the ways to turn it on.
References#
- sleep-guard-for-mac (GitHub)
- Apple Developer: IOPMLib.h (power management assertion types)
- Apple Support: Use Instant Hotspot to connect to your Personal Hotspot
- SwiftBar (GitHub)
- macOS manual pages:
man caffeinate,man pmset