I hand Claude Code a lot of long tasks: a refactor across a package, a test suite to fix, a migration that takes a while. The point is to walk away while it works. So I'd start the run, close my MacBook, and leave.
Then I'd come back to a session that had stalled a few minutes in.
This post covers why that happens, what you can do about it with built-in macOS commands, where those commands fall short, and the small menu bar app I ended up building. If you only want the commands, the first half is for you. You don't need the app.
Why closing the lid kills the run
When a Mac sleeps, Claude Code in your terminal stops too. The network drops, API requests fail, and the agent sits there until you wake the machine. Two things cause it: idle sleep (you step away long enough), and lid-close sleep (you shut the laptop).
macOS sleeps the Mac when you close the lid. The one built-in exception is clamshell mode, and that needs a power adapter, an external display, and an external keyboard or mouse all connected. Fine at a desk. Useless in a café, on a train, or when you just want to carry the laptop to another room.
Why caffeinate isn't enough
The first thing most of us try is caffeinate:
# keep the Mac awake while one command runs
caffeinate -i your-command
That's a good tool and I still use it for short jobs. But caffeinate, keep-awake apps, and the "Prevent automatic sleeping when the display is off" setting all target idle sleep. Lid-close sleep works separately, so in most setups the Mac still sleeps when you close it, and on battery with no external display it definitely does.
There's also a workflow problem. You have to remember to wrap every run, and an interactive Claude Code session isn't really "one command that exits."
The command that actually works: pmset disablesleep
The setting that also disables lid-close sleep is:
# disable sleep entirely, including when the lid closes
sudo pmset -a disablesleep 1
# turn sleep back on
sudo pmset -a disablesleep 0
This works. With it set, you can close the lid on battery and the Mac keeps running.
It also has real risks, and they're why I didn't want to just leave it on:
- It's easy to forget to undo. The agent finishes at 2 AM and your Mac keeps running. On battery, that means it drains.
- The setting even survives a restart. It's not tied to a session or a process. Whatever state you left it in stays until you change it.
- Heat. A MacBook that's awake and closed still makes heat. On a hard surface it vents from the bottom and back, which is fine. In a bag or under a blanket, it can't. A Mac you think is asleep in your backpack is the worst case.
If you go the DIY route, at least tie it to the job so it can't outlive it. A small wrapper with a trap turns sleep back on even if you Ctrl-C:
#!/bin/zsh
# awake-run: keep the Mac awake (lid closed too) only while a command runs
sudo pmset -a disablesleep 1
trap 'sudo pmset -a disablesleep 0' EXIT INT TERM
"$@"
awake-run claude
This still won't protect you from heat, a dying battery, or a crash that skips the trap, and you'll want to check the current state now and then:
pmset -g | grep -i sleep
But it's much better than leaving disablesleep 1 on and hoping you remember.
Knowing when the agent is actually done
The wrapper keeps the Mac awake for as long as the process lives. That's not the same as "while Claude is working." An interactive session can sit idle for hours after the task ends, and keeping the Mac awake all that time is just the "forgot to undo it" problem in another form.
Claude Code has hooks that fire on real events. A Stop hook runs when a task ends, and a Notification hook runs when Claude is waiting for your input or permission. Even without any app, you can get a macOS notification for both by putting this in ~/.claude/settings.json:
{
"hooks": {
"Stop": [{ "hooks": [{ "type": "command",
"command": "osascript -e 'display notification \"Claude Code finished\" with title \"Claude Code\"'" }] }],
"Notification": [{ "hooks": [{ "type": "command",
"command": "osascript -e 'display notification \"Claude Code needs you\" with title \"Claude Code\"'" }] }]
}
}
If you already have a settings file, merge in just the hooks key. It applies to sessions started afterwards.
The limits: you only see those notifications at your Mac, you still have to go back to the terminal to see what needs approval, and if the Mac has gone to sleep, the work has stopped anyway.
Hooks are the right signal, though. They tell you exactly when work starts and ends instead of guessing. That's what I built on.
What I built: Lidnight
Lidnight is a macOS menu bar app that puts those pieces together. It uses the same disablesleep mechanism, but it turns it on and off for you based on whether AI work is actually running.
Detection. When you turn on Claude Code in Settings › Agents, Lidnight adds Claude Code hooks for task start and end, so it knows exactly when Claude is working. Your settings file is backed up first, and turning it off removes only Lidnight's entries. Codex is integrated the same way. Other tools (Gemini CLI, Kiro CLI, Aider, Ollama, LM Studio, and others, 44 in total) are estimated from process activity, plus GPU use for local models. Multiple sessions are tracked separately, and the Mac sleeps only after the last one finishes.
Going back to sleep. In AI Auto mode, once everything has been quiet for 10 minutes, the Mac goes back to sleep normally. When it's unclear whether work is still going, it stays awake. I chose not cutting a run short over saving a few minutes of battery. There's also a Timer mode and an Until mode for tools it can't detect.
The risks from above. These are what I most wanted handled:
- If the battery stays above 42°C for over a minute, you get an alert. At 45°C, the Mac goes to sleep. It also sleeps when the battery hits a floor you set.
- If the app is force-quit, a watchdog process restores sleep settings. After a sudden power loss, they're restored at the next boot.
- It only undoes what it changed: sleep prevention, Low Power and performance modes, fan control, and brightness all go back to their previous values.
- Changing the setting needs admin rights, so it installs a small helper that only runs a fixed list of sleep, battery power mode, and fan commands. You can remove it in Settings.
To be clear about heat: the temperature cutoff is a safety net, not permission to put a running Mac in a bag. Keep it on a hard, ventilated surface.
Lid behavior. The default, "Agents use screen," keeps the display on at zero brightness without locking, so agents that control the screen keep working. That also means the session is unlocked while the lid is closed. If you don't want that, pick "Screen off," which turns the display off and locks.
Approving permission prompts without going back to the terminal
The other way a long run stalls is Claude stopping to ask "Can I run this?" while you're away.
Lidnight's hook shows what Claude wants to do (the command, the file it will edit, or the URL) and lets you allow or deny it from:
- a card in the menu bar window, buttons on the macOS notification, or a small bunny that shows up just left of the notch only while an agent is waiting (free)
- your phone, through the free ntfy app, after linking once with a QR code and no account (Pro)
The normal terminal prompt still appears, and whichever answers first wins.
Approving from a phone is the part I was most careful with:
- Push notifications never include the command.
- The status page is encrypted end to end on the Mac (AES-GCM), and the key reaches your phone only through the QR code, so the relay server can't read it.
- Allow and deny are one-time HMAC signatures. They expire and only work for that one request, so a copied one can't be replayed.
- Hard-to-undo commands like
rm -rf,git push, orsudocan't be allowed from the phone at all. Those stay Mac-only. - Phone commands only reach a Mac that's awake, and anything more than 10 minutes late is ignored.
Setup: turn on Claude Code in Settings › Agents, make sure "Approve from anywhere" is on (it's the default), and for your phone, turn on phone alerts in Settings › Alerts and scan the QR code. Claude sessions that were already open need one restart to pick it up.
Smaller things that came along
- A bunny per agent in the menu bar that runs faster as tokens flow, and gets an amber dot when an agent is waiting for approval.
- Claude and Codex 5-hour and weekly limits with reset times, plus token counts and estimated API-price cost by model and project, all read from local logs. These are estimates, not your actual bill.
- A CLI (
lidnight start,stop,status,doctor) so an agent can switch it on itself before a long job.lidnight doctorchecks the helper, sleep prevention, hooks, approvals, and phone link, one line each.
There's no account and no analytics, and your code and conversations never leave the Mac.
Try it, or just use the commands
If caffeinate -i for short jobs and the pmset wrapper for long ones cover what you need, use those. They're free and they work.
If you'd rather not think about it, Lidnight is at https://lidnight.app/en/, or:
brew install whereisdw/tap/lidnight
It needs an Apple Silicon MacBook on macOS 13 or later. The free tier includes AI Auto mode, battery and heat protection, and approvals on the Mac. Pro is a one-time $4.99 for one Mac ($9.99 for three), no subscription, and adds phone alerts and approvals, auto-start when AI work begins, fan boost, and longer stats. Every download includes a 7-day Pro trial with no payment details needed, and there's a 14-day refund.
Getting lid-closed sleep right was more subtle than I expected. If you've hit edge cases with pmset, clamshell mode, or hooks, I'd like to hear about them in the comments.
Top comments (0)