30-Oct-2026
- ๐ A reminder that came due while the Mac slept could launch during a Maintenance DarkWake, before the network was ready. Both icon downloads then timed out (about 20 seconds), both icons fell back to the Finder icon, and the dialog waited on a sleeping or locked display until the user returned (Issue #172; thanks, James Anzaldua!)
- DarkWake deferral:
dor-starter.zshno longer launchesdor.zshwhilepmset -g systemstatelistsCPUwithoutGraphics. It logs[NOTICE] DarkWake detected (system capabilities: โฆ); deferring reminder launch to the next heartbeat.and leavesdor-state.plistunchanged, includingLastBootReevaluation, so the due reminder runs on the first heartbeat after a full wake. Unrecognizedpmsetoutput fails open and launches as before. dor.zshrepeats the check just before it builds the dialog (after anyMeetingDelaywait). A DarkWake run logs[NOTICE] No reminder shown: the Mac is in a Maintenance DarkWake (system capabilities: โฆ)and, for starter-launched runs, schedules an exactNextScheduledReminder60 seconds later instead of the nextDailyReminderTimesslot. No mode bypasses this check, so pre-deadline threshold, aggressive, and Force reminders appear at the first full wake; a Force-mode timer can no longer count down to a restart on a display nobody can see. Demo mode is unchanged.- Force mode checks again before each redisplay in the same run and before a timer-driven restart, so a Force dialog that began during a full wake cannot redisplay or restart after the Mac enters a Maintenance DarkWake. The run logs
[NOTICE] Force restart redisplay deferred: the Mac is in a Maintenance DarkWake (system capabilities: โฆ); no restart requested.(orForce restart timer expiry deferred: โฆ), requests no restart, and, for starter-launched runs, schedules an exactNextScheduledReminder60 seconds later. Clicking Restart Now still restarts. - Icon downloads:
downloadBrandingAssetswaits up to 5 seconds forscutil -rto report the macOS icon host as reachable (if it never does, both downloads are skipped and the cache and fallbacks below apply), then downloads the overlay and macOS icons in parallel with--connect-timeout 4 --max-time 10 --retry 1. Only image content counts as success, so a captive-portal page is rejected. The curl error now appears in the[ERROR] Failed to download โฆline instead of on a separate raw line. - Icon cache: each successful download is copied to root-owned
/Library/Management/<rdnn>/dor-icon-macos-<major>.png(dor-icon-macos-default.pngwhen the major version is not numeric),dor-icon-overlay-light.png, ordor-icon-overlay-dark.png(not in demo mode). When a download fails, the cached copy is used.AllandScriptredeployments keep the cache;Uninstallremoves it. - Fallbacks: with no download and no cache, the macOS icon falls back to
/System/Library/CoreServices/Software Update.app(thenFinder.app), and the dialog shows no overlay icon instead of the Finder icon. An emptyOrganizationOverlayIconURLnow also shows no overlay icon.SwapOverlayAndLogokeeps the macOS icon as the primary icon when no overlay icon is available.Resources/reminderDialogPreferenceTest.zshpreviews the no-overlay andSwapOverlayAndLogofallbacks; it still hands the macOS icon URL to swiftDialog, so it does not preview theSoftware Update.appfallback. - No changes to preference keys, defaults, or DDM trust rules.
- Not covered: a locked screen or a sleeping display while the Mac is otherwise fully awake. These still show the reminder, which the user sees on return.
- DarkWake deferral:
- ๐ After a reboot, macOS can re-pad a past-due deadline to about one hour after login (
setPastDuePaddedEnforcementDate โฆ (usersLoggedIn=1)), butdor-starter.zshkept exiting quietly until the storedNextScheduledReminder, so the new deadline and its pre-deadline thresholds could pass with no reminder (Issue #165)- Once per boot, the first heartbeat that finds a console user other than
loginwindow,_mbsetupuser, orrootlaunchesdor.zsheven whenNextScheduledReminderis in the future. Heartbeats before login exit quietly, so the re-evaluation is not used up at the login window. - The starter records the boot in the new runtime-only
dor-state.plistkeyLastBootReevaluation(thekern.boottimeepoch) on any launch with a console user, and logs[NOTICE] Starter launch after boot; re-evaluating deadline (NextScheduledReminder: โฆ). - The post-boot run re-resolves the deadline, including the existing wait of up to 5 minutes for a refreshed padded date. It then shows a reminder only when a pre-deadline threshold, aggressive mode, or Force mode is due, or when the stored schedule has come due. Otherwise it moves
NextScheduledReminderearlier for a pending threshold or aggressive-mode start, never later, and logsNo reminder shown: post-boot re-evaluation found no reminder due. NextScheduledReminder=FALSE,dor.pidoverlap protection, manual runs, and demo runs are unchanged. No changes to preferences, defaults, or DDM trust rules.- Not covered: logging out and back in without a reboot.
- Once per boot, the first heartbeat that finds a console user other than
- Fixed: past-deadline aggressive mode was never scheduled when the next
DailyReminderTimesslot came after the aggressive-mode start, leaving a Mac past its deadline without reminders until the next baseline slot (for example, overnight after a final pre-deadline threshold reminder at 16:25 for a 16:29 deadline) (Issue #164)- The aggressive-mode start (effective deadline plus
AggressiveModePastDeadlineHours) is now a scheduling candidate alongside pending pre-deadline thresholds and baseline slots. The earliest wins. - Applies after pre-deadline threshold reminders, after regular reminders shown between the deadline and the aggressive-mode start, and to quiet-period and Update Tonight scheduling, which aggressive mode already bypasses at runtime.
- Skipped while
/Library/Management/<rdnn>/dor-aggressive-killexists; the next run re-evaluates once support removes it. - Runtime re-checks aggressive mode when the dialog returns. A reminder shown before the aggressive-mode start and answered after it now schedules
AggressiveModeFrequencyMinutesfrom that answer. Before, a pre-deadline threshold reminder left open past the aggressive-mode start (for example, the 16:25 threshold answered at 18:31) waited until the next baseline slot, and other reminders waited for the quiet period. Logged as(Scheduled aggressive-mode redisplay after late <n>-minute pre-deadline threshold acknowledgment)or(Scheduled aggressive-mode redisplay after return code <code>). The same re-check covers a dialog closed automatically for a pre-deadline threshold refresh but processed after the aggressive-mode start (for example, after sleep); a non-threshold dialog in that case logs(Scheduled aggressive-mode redisplay after dialog auto-refresh for <n>-minute pre-deadline threshold). - Logged as
[NOTICE] Scheduled next daemon reminder for โฆ (โฆ: aggressive-mode start (<n> hour(s) past effective deadline))or(Quiet-period bypass for aggressive-mode start โฆ). - No changes to preferences, defaults, DDM trust rules, or suppression rules.
- The aggressive-mode start (effective deadline plus
- Fixed: a Mac that already met its DDM declaration could still evaluate the DDM Emergency Fallback when the resolver rated its state
conflict. This happened whensoftwareupdatedloggedNo updates found for DDM to enforceafter the Mac installed its DDM-enforced update while install.log still showed the met declaration, or whensoftwareupdatedoffered a newer macOS (Available descriptor <offered> disagrees with DDM declaration <version>), for example macOS 27.0.1 with a 27.0.1 declaration while 27.2 is available. The run logged a[WARNING]and, whendor-fallback-declaration.plistexisted, evaluated the fallback. A fallback targeting a newer macOS could then show a reminder for a version DDM does not enforce.- When the conflict is only
Declaration persisted after 'No updates found for DDM to enforce'orAvailable descriptor <offered> disagrees with DDM declaration <version>, and the installed macOS meets that declaration, the resolver logs at[NOTICE](โฆ; macOS <installed> already meets it) instead of[WARNING]. - That case now skips fallback evaluation and logs
DDM Emergency Fallback not evaluated: macOS <installed> already meets DDM-enforced macOS <version> (DDM status=conflict)when a fallback plist exists. - The run ends with
No reminder shown: macOS <installed> already meets DDM-enforced macOS <version>; softwareupdated has nothing left to enforce (status=conflict)for a leftover declaration, orNo reminder shown: macOS <installed> already meets DDM-enforced macOS <version>; the different update softwareupdated offers (macOS <offered>) is not enforced by DDM (status=conflict)for a different offered update. The different-offered-update outcome was previously logged only when no fallback was configured. - Resolver status stays
conflict, so theDDM update check:line and both Pending-OS Extension Attributes are unchanged. Aconflictbetween two different declarations, andmissing,noMatch, andinvalidVersion, still evaluate fallback. - No changes to preference keys, defaults, or scheduling.
- When the conflict is only
- Fixed: a pre-deadline threshold reminder was recorded as delivered even when swiftDialog never showed it to the user (for example,
Return Code: 1while macOS was shutting down), so a reminder the user never saw used up that threshold for the deadline- Only user-interaction return codes (
0,2,3,4,10, the same codes that start the quiet period) and the open-dialog refresh now add a threshold toPreDeadlineThresholdDelivered. - Any other return code logs
[NOTICE] Pre-deadline threshold <n>-minute reminder not marked delivered; return code <code> is not a user interaction.The next run shows that threshold if it is still the latest one due, or records it inPreDeadlineThresholdSkippedonce a later threshold is due. - A starter-launched run then schedules an exact
NextScheduledReminder60 seconds later, logged as[NOTICE] Scheduled next daemon reminder for โฆ (Pre-deadline threshold <n>-minute reminder retry after return code <code>). Without the retry, a failed final threshold (for example, the 5-minute reminder) kept the schedule calculated before display, so the next reminder could wait until the aggressive-mode start. - The retry is skipped when it would land at or after the effective deadline, and for return code
9(SIGKILL), which is usually a persistent block.
- Only user-interaction return codes (
- Fixed: when a Mac woke from sleep after the effective deadline with a threshold reminder still open, the open-dialog check closed that reminder and nothing replaced it until the next scheduled run
- Once the effective deadline has passed, the open reminder now stays on screen, logged as
[NOTICE] Pre-deadline threshold <n>-minute reminder became due while a reminder dialog was open, but the effective deadline has passed; leaving the open dialog in place. - Before the deadline, a newly due threshold still closes the open dialog and shows the newer threshold reminder.
- Once the effective deadline has passed, the open reminder now stays on screen, logged as
- Fixed: when swiftDialog exited with code
9, the runtime loggedswiftDialog exited with code 9; confirm your .plist or .mobileconfig is installed:and the expected preference paths. Code9does not mean missing preferences:dialogclirelays the signal number whenDialogis killed, so9isSIGKILL, usually from a code-signature or endpoint-security block. Admins with valid preferences were sent in the wrong direction.- The
[WARNING]now readsswiftDialog exited with code 9; Dialog was killed (SIGKILL) before <user> could respond, usually by a code-signature or endpoint-security block. Next step: โฆ, naming acodesign --verifycheck onDialog.appand security or allowlisting tools. TheExpected managedPreferencesPlist/Expected localPreferencesPlist[INFO]lines are removed from this path. - Exit handling is unchanged: the run still ends with code
9, and the reminder is not marked as a user interaction. - No changes to preference keys, defaults, or scheduling.
- The
- Fixed:
launchDaemonManagement.zshcould report a failed swiftDialog install or upgrade as successful. It ignored theinstallerexit status, and after an upgrade it checked only thatdialogcliexisted, so a Mac that kept an older swiftDialog loggedswiftDialog version โฆ installed; proceeding...and finished deployment.- An
installerfailure now removes the temporary download and exits1with[FATAL ERROR] swiftDialog installer failed for <url>. - After installing, the script reads the
dialogcliversion again. It exits1with[FATAL ERROR] swiftDialog version '<version>' after install; swiftDialog 3.1.0.4994 or newer is required.when the version is empty or older thanswiftDialogMinimumRequiredVersion. - The swiftDialog check runs before the reset, so a failed install leaves the existing deployment in place. Rerun the deployment once swiftDialog
3.1.0.4994or later installs. - No changes to preference keys, defaults, or runtime behavior.
- An
- ๐ Re-assembling from a prior dev or test
.plist/.mobileconfiginto another lane carried itsAssembledorTESTplaceholder text into the new artifacts, so a dev configuration promoted with--lane prodstill showed that text in the production.plistand.mobileconfig(Issue #168)assemble.zshnow rewrites its own leading lane marker (Assembled, legacySample, orTEST, optionally after leading<br>tags) in imported string values to match the selected lane: removed forprod,TESTfortest, andAssembledfordev.- Matching is case-sensitive and anchored to the start of each value, so admin copy such as
Latest,Testing, orsampleelsewhere in a string is never changed. - Logged as
๐งน Normalized lane marker in <n> imported value(s) for '<lane>' lane. - A prior file ending in
-dev.plistor-test.pliststill selects that lane automatically; pass--lane prodto promote it. - A valid
--lanenow skips filename lane inference regardless of argument order, so--lane prodafter the file path no longer logs๐ Inferred deployment mode from prior plist: 'dev'before๐ฆ Deployment Mode: prod. - No changes to preference keys, defaults, or runtime behavior.
- Fixed: a missing or invalid
--lanevalue (for example,--lane production) printedDefaulting to interactive prompt., then re-parsed that value as a positional RDNN, soassemble.zshcould exit0with artifacts named for RDNNproduction.assemble.zshnow exits1withโ Invalid lane: '<value>'. Valid options: dev, test, prodbefore importing or generating anything. - ๐
Resources/diagnoseReminderState.zsh(1.6.5) adds--rdnn <rdnn>, so a Mac Admin running the helper on the affected Mac no longer types up to nine paths (Issue #166)- Each input that is not supplied is read from its live path: the effective
ScriptLog(Managed, then Local) or/var/log/<rdnn>.log,/var/log/install.log, both<rdnn>.dorm.plistpreference layers,dor-state.plist,dor-fallback-declaration.plist,SoftwareUpdateDDMStatePersistence.plist, the LaunchDaemon plist, and whetherdor-aggressive-killexists. - Explicit flags always win. An invalid RDNN exits
64; a missing client log orinstall.logexits66and names the path. The supplied RDNN joins RDNN detection, so a mismatch is still reported as aconflict. - Missing optional files are reported as
not present on this Mac(orunreadable on this Mac). A Mac with neither preference plist is reported with script defaults, which matches the runtime. - Fact-sheet and report-skeleton file statuses now read
supplied(wasreceived),not in upload,inferred (--rdnn),not present on this Mac, orunreadable on this Mac. - The fact sheet's Declaration candidates table now groups identical
install.logcandidates into one row with first seen, last seen, and count, so a fullinstall.logno longer produces hundreds of repeated rows. The fact-sheet title links to snelson.us/ddm, and the report's redaction sentence says "raw client files" instead of "raw uploads". install.logmarker headings and report bullets now quote the log text they match (for example,`No updates found for DDM to enforce` lines: none), so an empty section reads as "this log line was not found" rather than as a verdict.- Report section 7 (Do the client log and
install.logagree?) now opens with a helper table that puts the client log's lastDDM update check:result (version, declared deadline, status, source) beside the latestinstall.logdeclaration span. Pre-5.2.0logs use theirResolved DDM declarationlines. The agent still writes the Yes. / No. / Not comparable. answer. - Report section 8 (Likely cause and next steps) now opens with recorded evidence from the last run: its outcome, the runtime's own
No reminder shown:andNext step:text, the latest user action, the latest scheduling line, andNextScheduledReminder. The agent still writes the cause and next steps. - Non-root runs add a note that root-only files may be unreadable; run with
sudo. The helper stays read-only, the RDNN is never auto-discovered, and redaction is unchanged.
- Each input that is not supplied is read from its live path: the effective
- ๐
Resources/diagnoseReminderState.zshadds opt-in--no-redaction, so a Mac Admin diagnosing several Macs can tell the reports apart and match them to MDM records- Usernames, first names,
/Userspaths,install.logcomputer names, and serial numbers appear as logged. The fact-sheet header and report section 9 say the output is not redacted. - The fact-sheet and report-skeleton identity tables add Computer name and Serial number rows: from this Mac (
scutil --get ComputerName,IOPlatformSerialNumber) with--rdnn, otherwise from the latestinstall.logcomputer name and a labeled client-logSerial Number:line. - The suggested report filename adds the computer name and
-unredacted, for exampleddm-os-reminder-us.snelson-Mac-Lab-07-unredacted-2026-10-21-161500.md. - With
--rdnn, the full output is also saved to/Library/Management/<rdnn>/โฆ-unredacted-<timestamp>-fact-sheet.md(mode0600). The helper never creates that directory; a missing, symbolic-link, non-root-owned, or unwritable directory still prints the fact sheet and exits73. Without--rdnn, nothing is saved. - Default runs are unchanged: redacted, and they write nothing.
- Usernames, first names,
Resources/diagnoseReminderState.zshfollows the5.3.0runtime changes. Checklist updated toreminderDialog.zsh5.3.0; preference defaults are unchanged.- Reports
LastBootReevaluationand describes the post-boot exception in its starter rule. - Includes
DarkWakelines in the latest-run decision lines and describes the starter's DarkWake exit. A post-boot run's latest-run lines now start at the starter'sStarter launch after bootline and includePost-boot re-evaluationlines, which earlier helper versions omitted. - Lists the macOS updates
softwareupdatedoffered (PrimaryDescriptor: โฆ SU:install.log lines), grouped by descriptor, version, and build with first seen, last seen, and count. AconflictfromAvailable descriptor <offered> disagrees with DDM declaration <version>previously had no matching evidence in the fact sheet. - Fixed: the helper treated any script that shares
ScriptLogand logs<name> (<version>): โฆ [PRE-FLIGHT] Initiatingas a DDM OS Reminder run. On a Mac where another script ran last, the fact sheet reported that script's name and version, no resolver status, and its outcome as the last run. The helper now takes the runtime script name from DDM OS Reminder log lines, counts only that script's runs and Return Codes, and names the other scripts in an identity note.
- Reports
- Fixed:
Resources/createSelfExtracting.zsh(2.4.1) with noddm-os-reminder-*.zshinArtifacts/ran a barels -t, so it could encode an unrelated file from the current directory instead of stopping. It now exits withโ Error: No file matching 'ddm-os-reminder-*.zsh' found in โฆ. Generated wrappers are unchanged.
What's Changed
- 5.3.0 by @dan-snelson in #175
Full Changelog: v5.2.0...v5.3.0