You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Permissions changes: "Add, edit, and delete software" already defined in permissions table.
Changes to paid features or tiers: Fleet Premium
My device and fleetdm.com/better changes: My device changes specified in Figma.
Usage statistics: No changes
Other reference documentation changes: No changes.
First draft of test plan added
Once shipped, requester has been notified
Once shipped, dogfooding issue has been filed
Engineering
The delivery half of this story is built as a reusable platform, end_user_notifications, rather than as patch-specific code. Core handles enqueue, dispatch to Fleet Desktop, delivery confirmation, failure causes, and retry. A kind owns what to say, when, and what happens next. notify_before_patching is the first kind. #44672 and #39176 are both funded consumers.
Fleet Desktop already ships the notify command and the toast window from #49325 (PR #50211), so this story needs no Swift changes.
Test plan is finalized
Contributor API changes: No changes. The new endpoints are device-authenticated, not contributor API.
Load testing: Yes. A new cron runs every minute, which is what an accurate 5-minute reminder needs. idx_eun_dispatch (status, next_attempt_at) keeps the sweep cheap on an empty table. Measure the sweep against a fleet-sized end_user_notifications table.
Pre-QA load test: Yes. The cron is a permanent, always-on addition to the load profile, so measure it before QA rather than during.
Load testing/osquery-perf improvements: osquery-perf needs to produce app-open skips at scale, meaning hosts that report a failing patch policy and an empty pre-install query output. Without that the notification tables stay empty and the load test measures nothing.
This is a premium only feature: Yes
Open engineering questions
Marko answered most of these on 2026-08-11, in comments above and in Slack. What is left:
Fleet Desktop version dependency. The catalog manifest is pinned at 1.4.0, Info.plist says 1.5.0, and the deferred exit code needs a release beyond that. Three versions in play against a 2026-09-11 milestone. Decides: Marko, release.
[Activity] Patch policies: Install new version when app is closed #49106 names two fields wrong, and it is merged. Answering Marko's Slack question: the payload field is install_skipped_when_app_open and the policy field is patch_when_closed. skipped_install did ship, but as the frontend display status derived from the flag, not as a payload field. patch_only_when_closed never existed in code. So it is a doc fix, not a code rename, and 4.91 has not shipped yet. Handled in Notify before patching: documentation and engineering QA #50918.
Notification outcomes in the UI
No reason field. The activity carries script_execution_id, and the details modal calls GET /api/v1/fleet/scripts/results/{execution_id} for the exit code and output, picking its copy from the exit code (Figma dev note 5582:46519).
A deferral, where another toast was already on screen, arrives two ways. The binary reports the new exit code Marko added on 2026-08-11, with a script_execution_id. When the dispatcher catches it first no script runs at all, so absence of script_execution_id identifies it. That is unambiguous only because this kind sets no expiry, making a dispatcher-caught deferral the only outcome that emits an activity without a script run. Both render the same sentence.
The app-open skip keeps using installed_software with the skip flag for both options, told apart by pre_install_query_output.
Validation rules
Marko specified the exact user-facing text in a dev note on #50678. All three apply to the API and to fleetctl gitops.
Rule
Message
Both flags true
Only one of "patch_when_closed" or "notify_before_patching" can be set to true
Explicit continuous_automations_enabled: false
If "notify_before_patching" is true, "continuous_automations_enabled" can't be set to false.
Windows Fleet-maintained app
"notify_before_patching" is available for macOS Fleet-maintained apps. It's coming soon to Windows.
The second rejects an explicit false rather than overriding it silently. Omitting the field still auto-sets it. This mirrors patch_when_closed at server/service/global_policies.go:259 and pkg/spec/gitops.go:2082.
Dependency
Fleet Desktop 1.5.0 or newer is required on the host. The Fleet-maintained app manifest at ee/maintained-apps/outputs/fleet-desktop/darwin.json is still pinned at 1.4.0. The catalog has to pick up 1.5.0 before this feature works for anyone.
ℹ️ Please read this issue carefully and understand it. Pay special attention to UI wireframes, especially "dev notes".
Risk assessment
Requires testing in a hosted environment: No. Everything reproduces on a local Fleet with one enrolled Mac.
Requires load testing: Yes.
Risk level: High
Risk description: Three reasons. First, a new cron on a one-minute interval permanently changes the load profile. Second, the feature can quit an application the end user is working in, so a bug in either verify pass or in the per-app dedup guard closes an app without warning, or closes one the end user already updated. Third, it extends the app-open skip path added by Patch policies: Install new version when app is closed #39962, so a regression there affects patch_when_closed as well as this story. server/service/orbit_test.go:1368 is the guard test to watch.
Test plan
Make sure to go through the list and consider all events that might be related to this story, so we catch edge cases earlier.
Core flow
Add a Fleet-maintained app with notify before patching enabled. With the host online, the app open, and the policy failing, confirm the end user gets the 1 hour notification, then the 5 minute reminder, and the app closes and updates after that.
Same setup but with the app closed. Confirm it updates straight away and the end user is never notified.
Add three patch policies with notify before patching enabled and all three apps open. Confirm the end user gets one notification listing all three, not three separate ones.
Get the 1 hour notification, then go offline for a few hours. Confirm nothing updates while offline, and that a fresh 1 hour notification appears when the host comes back rather than the apps updating unannounced. Cycle is repeated 1hr notification, 5min reminder, install.
Get the notification, then update one of the listed apps yourself through My device > Self-service. Confirm it drops out of the 5 minute reminder and is not installed, while the others still update.
Get the notification, then update all the listed apps yourself. Confirm no 5 minute reminder appears, and nothing is installed.
While one notification's hour timer is still running, add a patch policy for another app and refetch the host. Confirm the new app gets its own separate notification and its own hour, and that the first apps still update on its original schedule.
Lock the screen, then refetch the host. Confirm no notification appears, nothing is installed, and the notification arrives on a later attempt once unlocked.
Log out entirely, then refetch the host. Confirm nothing is installed, and the notification arrives after logging back in.
Uninstall Fleet Desktop, then refetch the host. Confirm nothing is installed and Fleet reports it could not notify.
Confirm the countdown starts when the notification actually appears on screen, not when Fleet decided to send it.
Add a patch policy without notify before patching. Confirm it installs straight away with no notification, alongside policies that do notify.
With a notification on screen, click the Fleet Desktop icon in the Dock. Confirm the self-service window comes to the front and there is only one Fleet Desktop icon in the Dock.
Get 1 hour notification, click Update now, make sure that only Hide button is available in the toast. Install progress is visible. Timer is canceled, no 5 minute reminder.
Make sure the toast window notification UI respects the system theme set in macOS settings (dark / light mode).
Edge cases
TODO
Supplemental testing
Testing notes
Confirmation
Engineer: Added comment to user story confirming successful completion of test plan (include any special setup, test data, or configuration used during development/testing if applicable).
QA: Added comment to user story confirming successful completion of test plan.
QA: Determined whether this story needs Playwright automation.
Needs automation: Yes / No
If yes, filed a follow-up issue in the :help-qa project with status "Needs automation":
Goal
Changes
Product
notify_before_patchingin generated YAML files under Fleet-maintained apps.Engineering
The delivery half of this story is built as a reusable platform,
end_user_notifications, rather than as patch-specific code. Core handles enqueue, dispatch to Fleet Desktop, delivery confirmation, failure causes, and retry. A kind owns what to say, when, and what happens next.notify_before_patchingis the first kind. #44672 and #39176 are both funded consumers.Fleet Desktop already ships the
notifycommand and the toast window from #49325 (PR #50211), so this story needs no Swift changes.policies.notify_before_patching TINYINT(1) NOT NULL DEFAULT 0(Notify before patching: policy field, validation, and install-skip wiring #50911);end_user_notifications(Notify before patching: end-user notifications platform (delivery, device API, outcomes) #50910);patch_notificationsandpatch_notification_apps(Notify before patching: patch notification kind and activities #50912).idx_eun_dispatch (status, next_attempt_at)keeps the sweep cheap on an empty table. Measure the sweep against a fleet-sizedend_user_notificationstable.Open engineering questions
Marko answered most of these on 2026-08-11, in comments above and in Slack. What is left:
script_contentsstores a live credential and breaks if the token rotates mid-countdown. Blocks Notify before patching: end-user notifications platform (delivery, device API, outcomes) #50910. Decides: Jonathan, Lucas.notifyerror for "another notification is displayed" on 2026-08-11 but has not stated the value. Blocks Notify before patching: end-user notifications platform (delivery, device API, outcomes) #50910 and Notify before patching: activity feed, details modals, and automation runs #50915. Decides: Marko.Info.plistsays 1.5.0, and the deferred exit code needs a release beyond that. Three versions in play against a 2026-09-11 milestone. Decides: Marko, release.notify_before_patching: boolpending Rachael Shaw. If it becomes an enum, Notify before patching: policy field, validation, and install-skip wiring #50911, Notify before patching: End user experience dropdown across software and policy forms #50914, and Notify before patching: GitOps and generate-gitops support #50917 all change field names.install_skipped_when_app_openand the policy field ispatch_when_closed.skipped_installdid ship, but as the frontend display status derived from the flag, not as a payload field.patch_only_when_closednever existed in code. So it is a doc fix, not a code rename, and 4.91 has not shipped yet. Handled in Notify before patching: documentation and engineering QA #50918.Notification outcomes in the UI
No
reasonfield. The activity carriesscript_execution_id, and the details modal callsGET /api/v1/fleet/scripts/results/{execution_id}for the exit code and output, picking its copy from the exit code (Figma dev note5582:46519).A deferral, where another toast was already on screen, arrives two ways. The binary reports the new exit code Marko added on 2026-08-11, with a
script_execution_id. When the dispatcher catches it first no script runs at all, so absence ofscript_execution_ididentifies it. That is unambiguous only because this kind sets no expiry, making a dispatcher-caught deferral the only outcome that emits an activity without a script run. Both render the same sentence.The app-open skip keeps using
installed_softwarewith the skip flag for both options, told apart bypre_install_query_output.Validation rules
Marko specified the exact user-facing text in a dev note on #50678. All three apply to the API and to
fleetctl gitops.Only one of "patch_when_closed" or "notify_before_patching" can be set to truecontinuous_automations_enabled: falseIf "notify_before_patching" is true, "continuous_automations_enabled" can't be set to false."notify_before_patching" is available for macOS Fleet-maintained apps. It's coming soon to Windows.The second rejects an explicit
falserather than overriding it silently. Omitting the field still auto-sets it. This mirrorspatch_when_closedatserver/service/global_policies.go:259andpkg/spec/gitops.go:2082.Dependency
Fleet Desktop 1.5.0or newer is required on the host. The Fleet-maintained app manifest atee/maintained-apps/outputs/fleet-desktop/darwin.jsonis still pinned at 1.4.0. The catalog has to pick up 1.5.0 before this feature works for anyone.Risk assessment
patch_when_closedas well as this story.server/service/orbit_test.go:1368is the guard test to watch.Test plan
Core flow
Edge cases
Supplemental testing
Testing notes
Confirmation