Repository navigation
Lid: a timer, heat or battery end with the lid closed puts the Mac to sleep - #138
stevehoyek wants to merge 9 commits into
Conversation
macOS asks for a closed lid's sleep once, at the close, and disablesleep 1 refuses it. disablesleep 0 does not make it ask again, so a Mac ended for heat or a dying battery kept running in the bag until the lid opened. Claude-Session: https://claude.ai/code/session_01Jco1ESE6NxipTXFsZE31rZ
After a failed disablesleep 0, macOS refuses the request, and the recovery agent that later restores sleep does not ask. A refused request now asks again every 30 s while the lid stays closed and no session starts. Also tests an end that cannot remove session.json and a quit during an end. Claude-Session: https://claude.ai/code/session_01Jco1ESE6NxipTXFsZE31rZ
main's README is now short (krishhgg#65) and its lid-close detail lives in docs/lid-close.md, so this PR's sentence about a battery or heat end with the lid closed moves there. The README's battery and heat paragraph gets one short line saying the Mac goes to sleep. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Fixes the three review findings on the retry. The sleep is now owed from the floor end, not tied to one end's reason, so a quit that replaces the pending end no longer drops it. It is asked only with no session, no lifecycle operation queued or running, and no live sudo pmset, so it never lands inside a queued start. A start clears it only once it holds the lock, so a start refused for a busy lock keeps it. Claude-Session: https://claude.ai/code/session_01Jco1ESE6NxipTXFsZE31rZ
…uit waits for it A timer end with the lid closed left the Mac awake for hours when another app held off idle sleep (krishhgg#94). A quit whose disablesleep 0 failed again exited and dropped the owed sleep, which only the app asks for. Claude-Session: https://claude.ai/code/session_01Jco1ESE6NxipTXFsZE31rZ
The only conflict was in the imports: IOKit for FakeClosedLidSleep on this branch, Security from krishhgg#100 on main. Both stay. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…t at launch The three fixes from the 18 Oct review of krishhgg#61: - A sleep macOS never grants no longer holds every quit. After 10 refused requests (at most about 5 minutes at the 30 s retry) the owed sleep is given up and logged, and a quit no longer waits for it. A postponed request does not count, and a newly owed sleep starts a new count. AppDelegate asks SessionManager.letsQuitGo(after:). - A cleanup retry no longer asks again. Only an end that finds the session owes the sleep, so a retry of an end that has run (session.json kept in place) owes nothing new. - A launch that finds session.json expired owes the sleep for that timer end, and asks once the reconcile has released the lock. A request also waits until a sudo pmset left running has released the lock and its end has begun, not only until it exits. Co-Authored-By: Claude Opus 5.5 <[email protected]>
From Greptile's review of krishhgg#138: sleepClosedLidIfOwed saw an open lid only when an ask ran, and the end had already stopped the session's lid observer. A lid opened and closed again between two asks, or while the end waited for a sudo pmset left running, went unseen, and the next ask put the Mac to sleep. SessionManager now watches the lid with a LidObserver of its own from the end that owes the sleep until the sleep is dropped. An open that holds for the 2 s debounce drops it, and closing the lid again does not bring it back. Every place that dropped the sleep goes through dropClosedLidSleep, which also cancels the next ask and stops the watcher. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Real-Mac report for 98d0b74Build. A clean checkout of 98d0b74, installed with Mac. No external display. Times in A timer or floor-rule end with the lid closed puts the Mac to sleep: passThree runs, all ending with the lid closed. Each time the log says
The sampler was not running for the two battery runs. The Also seen: a session started at 8%, under the floor, with the lid open, ended at once and asked for no sleep. The same end with its
|
What changes
This continues #61 by @shikhar1729, who handed it over here. The six commits from #61 are unchanged. On top of them:
TestSupport.swift.sudo pmsetleft running does not count, and a newly owed sleep starts a new count.sudo pmsetleft running has released the lock and its end has begun, not only until it exits.Fixes #94 while the app runs. As in #61, a session the recovery agent ends after a crash still leaves a closed Mac awake. That needs a change to
backstop.shand is not in this PR.#81 builds on #61, so it will need to move onto this branch.
How it was tested
ClosedLidSleepTests:testSleepThatIsNeverRestoredIsGivenUpAndTheQuitGoes,testPostponedRequestsDoNotCountTowardTheBound,testANewOwedSleepStartsANewCount,testExpiredSessionAtLaunchWithTheLidClosedAsksForSleep,testExpiredSessionAtLaunchAsksOnceALiveCommandExits,testExpiredSessionAtLaunchAsksWhenTheReconcileRunsAgainandtestLaunchReconcileWithTheLidOpenOrNoSessionAsksNothing.testEndThatCannotRemoveTheSessionFileAsksOnceOverItsRetries(was...StillAsks) now runs over several retries and checks that exactly one sleep request succeeds.swift test(1,296 tests, 13 skipped for tmux, which this Mac doesn't have) andswift build -c release -Xswiftc -warnings-as-errorspass locally, on Xcode 27.1 and macOS 26.6.2.docs/releasing.md: the launch path reaches the sleep through the sameIOPMSleepSystemcall as the existing closed-lid item, and the tests above cover how it gets there.Real-Mac evidence
I'll post the report from my MacBook as a comment at the final head, once CI and the reviews are done.
🤖 Generated with Claude Code


Confidence Score: 5/5
The PR appears safe to merge; the previous lid-opening cancellation issue is fixed.Summary
Insomnia asks macOS to sleep when a timer or battery or heat rule ends a session with the lid closed.
No new actionable issues were found. The previous finding is fixed.
Diagram
%%{init: {'theme': 'neutral'}}%% flowchart TD A[Timer or floor rule ends session] --> B[Watch lid while sleep is owed] B --> C[Restore changes and finish queued work] B --> D[Lid opening passes debounce] D --> E[Cancel owed sleep and stop watcher] C --> F{Sleep still owed and lid closed?} F -->|Yes| G[Ask macOS to sleep] F -->|No| E G -->|Granted or refused 10 times| E G -->|Refused fewer than 10 times| H[Retry later] H --> FReviews (2) · Last reviewed commit: "Lid: an open drops the owed closed-lid s..." · Reviewed by Greptile