github dan-snelson/DDM-OS-Reminder v5.3.0b4
DDM OS Reminder (5.3.0b4)

pre-release5 hours ago

More racing stripes for Mac Admins' favorite "set-it-and-forget-it" macOS update reminder: reminders now wait out Maintenance DarkWake, re-check deadlines after reboot, and keep your branding when the network lags, thanks to cached icons

26-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.zsh no longer launches dor.zsh while pmset -g systemstate lists CPU without Graphics. It logs [NOTICE] DarkWake detected (system capabilities: โ€ฆ); deferring reminder launch to the next heartbeat. and leaves dor-state.plist unchanged, including LastBootReevaluation, so the due reminder runs on the first heartbeat after a full wake. Unrecognized pmset output fails open and launches as before.
    • dor.zsh repeats the check just before it builds the dialog (after any MeetingDelay wait). A DarkWake run logs [NOTICE] No reminder shown: the Mac is in a Maintenance DarkWake (system capabilities: โ€ฆ) and, for starter-launched runs, schedules an exact NextScheduledReminder 60 seconds later instead of the next DailyReminderTimes slot. 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.
    • Icon downloads: downloadBrandingAssets waits up to 5 seconds for scutil -r to 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.png when the major version is not numeric), dor-icon-overlay-light.png, or dor-icon-overlay-dark.png (not in demo mode). When a download fails, the cached copy is used. All and Script redeployments keep the cache; Uninstall removes it.
    • Fallbacks: with no download and no cache, the macOS icon falls back to /System/Library/CoreServices/Software Update.app (then Finder.app), and the dialog shows no overlay icon instead of the Finder icon. An empty OrganizationOverlayIconURL now also shows no overlay icon. SwapOverlayAndLogo keeps the macOS icon as the primary icon when no overlay icon is available. Resources/reminderDialogPreferenceTest.zsh previews the no-overlay and SwapOverlayAndLogo fallbacks; it still hands the macOS icon URL to swiftDialog, so it does not preview the Software Update.app fallback.
    • Resources/diagnoseReminderState.zsh (1.6.3) includes DarkWake lines 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's Starter launch after boot line and include Post-boot re-evaluation lines, which earlier helper versions omitted. Checklist updated to reminderDialog.zsh 5.3.0b4.
    • 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.
  • ๐Ÿ†• Re-assembling from a prior dev or test .plist / .mobileconfig into another lane carried its Assembled or TEST placeholder text into the new artifacts, so a dev configuration promoted with --lane prod still showed that text in the production .plist and .mobileconfig (Issue #168)
    • assemble.zsh now rewrites its own leading lane marker (Assembled, legacy Sample, or TEST, optionally after leading <br> tags) in imported string values to match the selected lane: removed for prod, TEST for test, and Assembled for dev.
    • Matching is case-sensitive and anchored to the start of each value, so admin copy such as Latest, Testing, or sample elsewhere 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.plist or -test.plist still selects that lane automatically; pass --lane prod to promote it.
    • A valid --lane now skips filename lane inference regardless of argument order, so --lane prod after 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.
  • ๐Ÿ†• A missing or invalid --lane value (for example, --lane production) printed Defaulting to interactive prompt., then re-parsed that value as a positional RDNN, so assemble.zsh could exit 0 with artifacts named for RDNN production. assemble.zsh now exits 1 with โŒ Invalid lane: '<value>'. Valid options: dev, test, prod before importing or generating anything.
  • Fixed: after a reboot, macOS can re-pad a past-due deadline to about one hour after login (setPastDuePaddedEnforcementDate โ€ฆ (usersLoggedIn=1)), but dor-starter.zsh kept exiting quietly until the stored NextScheduledReminder, 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, or root launches dor.zsh even when NextScheduledReminder is 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.plist key LastBootReevaluation (the kern.boottime epoch) 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 NextScheduledReminder earlier for a pending threshold or aggressive-mode start, never later, and logs No reminder shown: post-boot re-evaluation found no reminder due.
    • NextScheduledReminder=FALSE, dor.pid overlap 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.
    • Resources/diagnoseReminderState.zsh (1.6.1) reports LastBootReevaluation and describes the post-boot exception in its starter rule. Checklist updated to reminderDialog.zsh 5.3.0b2.
  • Fixed: past-deadline aggressive mode was never scheduled when the next DailyReminderTimes slot 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-kill exists; the next run re-evaluates once support removes it.
    • 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.
  • Fixed: a pre-deadline threshold reminder was recorded as delivered even when swiftDialog never showed it to the user (for example, Return Code: 1 while 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 to PreDeadlineThresholdDelivered.
    • 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 in PreDeadlineThresholdSkipped once a later threshold is due.
  • 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.
  • Resources/diagnoseReminderState.zsh (1.5.0) 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.plist preference layers, dor-state.plist, dor-fallback-declaration.plist, SoftwareUpdateDDMStatePersistence.plist, the LaunchDaemon plist, and whether dor-aggressive-kill exists.
    • Explicit flags always win. An invalid RDNN exits 64; a missing client log or install.log exits 66 and names the path. The supplied RDNN joins RDNN detection, so a mismatch is still reported as a conflict.
    • Missing optional files are reported as not present on this Mac (or unreadable 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 (was received), not in upload, inferred (--rdnn), not present on this Mac, or unreadable on this Mac.
    • The fact sheet's Declaration candidates table now groups identical install.log candidates into one row with first seen, last seen, and count, so a full install.log no 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.log marker 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.log agree?) now opens with a helper table that puts the client log's last DDM update check: result (version, declared deadline, status, source) beside the latest install.log declaration span. Pre-5.2.0 logs use their Resolved DDM declaration lines. 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: and Next step: text, the latest user action, the latest scheduling line, and NextScheduledReminder. 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.
    • Checklist updated to reminderDialog.zsh 5.3.0b1; preference defaults are unchanged.
  • Resources/diagnoseReminderState.zsh (1.6.0) adds 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, /Users paths, install.log computer 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 latest install.log computer name and a labeled client-log Serial Number: line.
    • The suggested report filename adds the computer name and -unredacted, for example ddm-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 (mode 0600). The helper never creates that directory; a missing, symbolic-link, non-root-owned, or unwritable directory still prints the fact sheet and exits 73. Without --rdnn, nothing is saved.
    • Default runs are unchanged: redacted, and they write nothing.
  • Fixed: Resources/createSelfExtracting.zsh (2.4.1) with no ddm-os-reminder-*.zsh in Artifacts/ ran a bare ls -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.

Don't miss a new DDM-OS-Reminder release

NewReleases is sending notifications on new releases.