My VM Was Frozen Half the Time and Nothing Told Me

I run Linux VMs under Lima on a MacBook Pro that lives on mains with the lid closed, and I reach them over Tailscale from elsewhere. SSH into them would hang. Not always — sometimes.

There was no error to chase. No refused connection, no timeout, no log line. The host was up, limactl list said Running, and half the time it worked fine.

Prior art first, so you can leave if that’s what you need: the fix is sudo pmset -a disablesleep 1, and Sleepless already documents that properly — what the flag does, why assertion-based apps like Amphetamine and KeepingYouAwake lose at lid-close, and that it works on Apple silicon. I’m not going to re-explain any of it.

This post is the part I couldn’t find anywhere: that a Virtualization.framework guest can lose half its wall-clock life to the host’s own maintenance sleeps without emitting a single error, how to detect that, and what the same flag does to Power Nap.

Frozen half its life

When the host sleeps, Virtualization.framework suspends the guest and its clock stops. tailscaled inside the guest notices:

monitor: time jump detected (slept 15m20s), probably wake from sleep
monitor: time jump detected (slept 16m8s),  probably wake from sleep
monitor: time jump detected (slept 17m23s), probably wake from sleep

Nineteen in one day. 235 minutes — suspended for roughly half its wall-clock life, in twelve-to-seventeen minute blocks with one or two minutes of consciousness in between. SSH arriving during a block has nothing to answer it. That’s the entire bug, and it never produced an error message.

The host wasn’t idle-sleeping, which is what I’d have expected. It was taking scheduled maintenance sleeps:

Sleep  Entering Sleep state due to 'Maintenance Sleep':TCPKeepAlive=active
Sleep  Entering Sleep state due to 'Sleep Service Back to Sleep'

57 of my 63 sleep events since boot were those two — while Amphetamine held PreventUserIdleSystemSleep continuously for 23h58m. The app wasn’t broken. That assertion has no say over this category, so it sat there being held while the machine slept 57 times underneath it.

Two ways to detect it

If the guest runs Tailscale, it’s free:

sudo journalctl -b | grep 'time jump detected'

If it doesn’t, uptime lies — and the lie is the measurement. /proc/uptime doesn’t advance while the guest is frozen, so uptime -s reported a boot time 3h50m later than the journal’s first entry for that same boot, almost exactly the 3h55m of measured freeze:

uptime -s
sudo journalctl -b -o short-iso | head -1

Watch that gap over a day. If it grows, you’re being frozen. It needs nothing installed and works on any Linux guest.

Every resume costs more than the freeze. tailscaled follows each time jump with LinkChange: major, rebinding … rebind-reason=[time-jumped(16m3s)], so connections that survived the freeze get torn down on the way out.

One trap if you go measuring on the host: pmset -g log retains about a week of history, not just the current boot. I divided a week of events by one boot’s uptime and scared myself with a number six times too large. Filter by date and cross-check Total Sleep/Wakes since boot.

Does the flag stop Power Nap too?

This is the question I couldn’t find answered. disablesleep obviously stops idle and lid-close sleep. But does it stop the maintenance cycles — the ones doing the damage here? Nobody I could find had run SleepDisabled=1 and reported whether Maintenance Sleep still shows up in pmset -g log.

It stops them, with powernap left at 1. Eighty minutes after setting the flag, no console input for the whole window:

host sleep events since the flag:  0
VM time-jump events since flag:    0

The pre-flag cadence was metronomic — one freeze roughly every 17 minutes: 05:41, 05:58, 06:17, 06:35, 06:52, 07:09, 07:27, 07:45, 08:01, 08:18, 08:35. That window should have produced four or five. It produced none, and the uptime gap agreed independently by holding at 3h50m instead of growing.

Which makes sense: Apple documents Power Nap as activating “when your Mac goes to sleep”. Veto the transition and nothing is left to trigger it. So you don’t need to disable Power Nap — just as well, since on Apple silicon you can’t.

Honest limit: eighty minutes is four or five expected events, not the dozen an overnight run gives, on one M3 Max on one OS version.

What the source says that the write-ups don’t

The flag is usually described as a lid-close trick. The kernel is broader than that. In XNU’s IOPMrootDomain.cpp the gate carries Apple’s own comment:

        if (userDisabledAllSleep)
        {
            err = 1;        // 1. user-space sleep kill switch

The comment above it scopes that gate to “idle and demand system sleep”, which undersells its own call sites. Grep them and you find kIOPMSleepReasonClamshell, kIOPMSleepReasonLowPower, kIOPMSleepReasonPowerButton — and kIOPMSleepReasonThermalEmergency.

That last one matters, and I haven’t seen it mentioned anywhere. The kill switch refuses thermal-emergency sleep too. So the flag doesn’t merely keep your Mac awake; it declines the sleep the machine would otherwise use to cool itself. Minutes after mine went on, 114 of these appeared in a two-minute burst:

ThermalEvent  Ignored DarkWake thermal emergency signal

Nothing came of it — pmset -g therm showed no warning and no throttling, and it never repeated. A hard SMC-level shutdown isn’t a sleep request, so that backstop survives, but it’s a blunter one. If you’re doing this to a closed laptop, watch the temperature. (Caveat: that source tree is older than what ships on 26.6.1.)

Three small corrections to what’s written down

  • The plist has moved. Every source I found — Wikipedia, DSSW — says pmset settings persist in /Library/Preferences/SystemConfiguration/com.apple.PowerManagement.plist. On 26.6.1 that path doesn’t exist. It’s now /Library/Preferences/com.apple.PowerManagement.plist. An NVRAM reset still wipes it.
  • pmset darkwakes isn’t a key on 26.6.1 — not in the man page, not in pmset -g custom. If you’ve seen it suggested, it won’t do anything here.
  • Masking the guest’s sleep.target is a real fix for a different Lima problem, not this one. My guest never self-suspended: systemd-suspend.service had no entries at all. It was frozen from outside, and nothing inside Linux can refuse that.

Takeaway

The interesting failure wasn’t a sleep setting. It was that a machine can lose half its life to suspension and report nothing at all. pmset -g log is ground truth for what the host did; if there’s a VM involved, its own clock is ground truth for what it lost.

comments powered by Disqus