How your Mac can update macOS when you don’t want it to

Over the last few years, many have reported that their Macs spontaneously updated or even upgraded macOS when they didn’t expect them to, and often against their wishes. This can occur when Software Update in System Settings has Install macOS updates turned off. Explanations of how Apple appears to be able to override that setting have so far been lacking; this article explains how it happened overnight to my iMac Pro, when it updated itself from Sequoia 15.1.1 to 15.3.1.
Spontaneous macOS update
My story will be familiar to others who have suffered a similar forced update: I came down in the morning to discover my iMac Pro had shut down when I had left it running just seven hours earlier. A little detective work in its log revealed what had happened while I had been asleep.
Although I have kept this iMac Pro up to date with macOS since I first installed Mojave on it on 18 November 2018, it’s growing old and a bit creaky, and the update to Sequoia 15.1.1 on 19 November last year was slightly traumatic, with a series of shutdowns rather than restarts. I therefore decided to leave it running 15.1.1 until I had completed migrating to my Mac mini M4 Pro this Spring.
As a result, it had periodically notified me of updates to 15.2, 15.3, and most recently 15.3.1, each of which I had politely declined. Those notifications became more persistent, and one or two gave me either of two options, to update now, or later that night, and couldn’t otherwise be dismissed. I therefore chose to defer the update until the night, and nothing came of them.
One of those notifications, though, decided to end my procrastination and added a background activity named com.apple.SUOSUScheduler.tonight.install to the DAS-CTS scheduling system. In the small hours of the morning, DAS rescored its list of activities, and decided that it was time to dispatch that task, writing these entries in the log:
01:39:07.463 com.apple.duetactivityscheduler Rescoring all 499 activities [Entered SmartPowerNap]
01:39:07.490 com.apple.duetactivityscheduler 0:com.apple.SUOSUScheduler.tonight.install:3370CB:[{name: Thermal Policy, policyWeight: 5.000, response: {0, 0.20, }}, Decision: CP Score: 0.915843}
01:39:07.490 com.apple.duetactivityscheduler ‘0:com.apple.SUOSUScheduler.tonight.install:3370CB’ CurrentScore: 0.915843, ThresholdScore: 0.162610 DecisionToRun:1
01:39:07.589 com.apple.xpc.activity Initiating: com.apple.SUOSUScheduler.tonight.install (0x7fd1d582a720)
That activated SoftwareUpdate, which ignored my user settings and proceeded to install the update:
01:39:07.589 com.apple.SoftwareUpdate SUOSUTonightObserver: Tonight activity fired!
01:39:07.590 com.apple.powerd Process softwareupdated.308 Created MaintenanceWake “com.apple.SoftwareUpdate.TonightActivityTrigger” age:00:00:00 id:55834610938 [System: SRPrevSleep kCPU]
01:39:07.590 com.apple.SoftwareUpdate SUOSUTonightObserver: Proceeding with updates
01:39:07.590 com.apple.SoftwareUpdate SUOSUServiceDaemon: Chose on-console client: SoftwareUpdateNotificationManager (type = sunm, pid = 698, uid = 501, path = /System/Library/PrivateFrameworks/SoftwareUpdate.framework/Versions/A/Resources/SoftwareUpdateNotificationManager.app/Contents/MacOS/SoftwareUpdateNotificationManager)
It turns out that these ‘DoItLater’ or ‘installTonight’ updates are already well provided for by SoftwareUpdate:
01:39:08.243 com.apple.SoftwareUpdate Successful call to startInstallingDoItLaterUpdates
01:39:08.244 com.apple.SoftwareUpdate SUOSUShimController: Start installing updates: {(<SUOSUProduct: MSU_UPDATE_24D70_patch_15.3.1_minor>)}, options: { DoInForeground = 0; DoItLater = 1; ForceRestart = 0; InitiatingClient = 2; MDMInitiated = 0;}
01:39:08.245 com.apple.SoftwareUpdate SUOSUMobileSoftwareUpdateController: Updating additionalUpdateMetricEventFields: {autoUpdate = false; buddy = false; commandLine = false; ddm = false; installTonight = true; mdm = false; notification = true; settings = false; }
The update process carefully avoided revealing what was about to happen:
01:39:08.362 com.apple.SoftwareUpdate MSU update already prepared, skip showing license agreement
01:39:08.396 com.apple.SoftwareUpdate Skipping showing the SLA
01:39:08.396 com.apple.SoftwareUpdate SUOSUShimController: Updates require post-install action (restart), installing now
01:39:08.396 com.apple.SoftwareUpdate SUOSUShimController: MSU update already prepared
01:39:08.396 com.apple.SoftwareUpdate SUOSUShimController: Download required: 0 (legacy=0, MSU=0)
01:39:08.396 com.apple.SoftwareUpdate SUOSUShimController: Notification manager client, proceeding with countdown notification flow without confirmation
01:39:08.397 com.apple.SoftwareUpdate SUOSUNotificationUpdateService: Install did begin for updates: ( “<SUOSUProduct: MSU_UPDATE_24D70_patch_15.3.1_minor>”)
Local authentication was also disabled to ensure that nothing stood in the way of the imminent update:
01:39:08.443 com.apple.SoftwareUpdate SUAppStoreUpdateController: authorize
01:39:08.508 com.apple.SoftwareUpdate SUOSUAuthenticationManager: Disabling local authentication requirement
01:39:08.556 com.apple.SoftwareUpdate SUOSUMobileSoftwareUpdateController: Download finished: (null)
01:39:08.557 com.apple.SoftwareUpdate SUOSURestartCountdownOperation: Successful downloads, proceed to countdown (downloadOnly=0)
01:39:08.561 com.apple.SoftwareUpdate SUOSURestartCountdownOperation: Starting restart countdown
01:39:09.713 com.apple.SoftwareUpdate SUOSUCountdownNotification: seconds remaining: 60
01:40:08.570 com.apple.SoftwareUpdate SUOSUCountdownNotification: seconds remaining: 1
01:40:08.573 com.apple.SoftwareUpdate SUOSUUpdateController: Sending authorization to softwareupdated
01:40:08.598 com.apple.SoftwareUpdate SUOSUUpdateController: Successfully authorized with softwareupdated
01:40:08.953 com.apple.SoftwareUpdate Restarting for software update (forced=0)
01:40:10.958 com.apple.SoftwareUpdate Starting post-logout mode (skipConfirm = 1, reconnectMode = 0, shouldShutdown = 0)
01:40:10.958 com.apple.SoftwareUpdate SUOSUAuthenticationManager: Disabling local authentication requirement
01:40:11.523 com.apple.SoftwareUpdate SUOSUAuthorizationController: Non-interactive authorization succeeded for non-admin user
01:40:11.523 com.apple.SoftwareUpdate SUOSUUpdateController: Sending authorization to softwareupdated
01:40:11.562 com.apple.SoftwareUpdate SUOSUUpdateController: Successfully authorized with softwareupdated
01:40:42.389 com.apple.SoftwareUpdate SUHelper: Rebooting (success = 1, night install = 1, shutdown = 1)
01:40:42.396 com.apple.SoftwareUpdate SUHelper: Preparing for night install
What happened next was unexpected by macOS, though:
01:40:46.875 === system wallclock time adjusted
and that was the last entry in the log until the initial kernel boot entry over five hours later when I started the Mac up:
06:57:24.842 === system boot: 78F481CC-E26F-464C-BEB7-6E26E49DB8DC
At 03:15 and 04:15 that morning, full backups should have been made of the Data and another working volume. Because at that time the Mac was shut down in the middle of a possibly failed update, those backups were never made. Thankfully when it started up just before 07:00 it was able to complete the update and then resumed normal service.
Points of note
Software update notifications tricked the user into unwittingly agreeing to perform a macOS update.
That update was expressly against Software Update settings.
The user was given no second chance to confirm they intended the update to take place.
The update was scheduled to be performed when the Mac was unattended.
DAS scheduling and dispatch were unaware of the scheduled backups to be performed later that night, and dispatched the update at a time before those backups were scheduled.
As the update was scheduled to be performed unattended, no warning was given when it was about to start, and there was no opportunity to delay the update until after backups had been completed.
Once the update had been scheduled by DAS, the only way to postpone or abort it would have been to shut the Mac down. Activities scheduled by DAS-CTS are hidden from the user, who has neither awareness nor control over them.
The overall effect is that macOS enforces updates on the user against their express settings, without giving them the opportunity to postpone or abort the update.