Per-user LaunchAgent
This guide is for MDM admins who want App Auto-Patch (AAP) to run continuously in every user’s session — auto-launching at login, restarting on unexpected exits, and surfacing pending updates via the menu bar item + user notification without ever popping the dashboard window in the user’s face. It’s the right deployment shape for managed fleets and is what TechTrekkie originally asked for in issue #33.
Deploy three things in this order:
- The AAP
.pkg(any current build, via Jamf / Munki / Intune / etc.). - A configuration profile with the AAP settings you want
centrally managed (e.g.
IgnoredLabels, deferral deadlines, support-team info — seeProfile Manifests/README.mdfor the schema). As of build 40 you do NOT need to explicitly setManagedSilentLaunchEnabled = true— the moment any AAP-aware managed preference is present, silent launch flips on by default. If you want managed Macs to keep the dashboard visible at launch, deployManagedSilentLaunchEnabled = falseexplicitly. - The LaunchAgent plist in this folder
(
xyz.techitout.appAutoPatch.agent.example.plist) dropped at/Library/LaunchAgents/xyz.techitout.appAutoPatch.agent.plist, ownerroot:wheel, mode0644. After the next user login, AAP launches silently, runs discovery + update check, and surfaces a user notification when pending updates are found. The user can open the dashboard from the menu bar item (Open App Auto-Patch) or by double-clicking the .app from Finder / Launchpad / Dock — both paths work normally; the silent-launch suppression only applies to the auto-opened window at process start.
Why a LaunchAgent and not a LaunchDaemon
Section titled “Why a LaunchAgent and not a LaunchDaemon”This was the cause of the “Setup Required” state TechTrekkie
reported when he tried to keep AAP running via a system LaunchDaemon
with UserName = root.
App Auto-Patch has two cooperating process types, and they have
different lifecycle owners:
- The root helper LaunchDaemon
(
xyz.techitout.appAutoPatch.roothelper) is what runs Installomator, installs packages, and writes receipts. It’s already deployed by AAP at install time via Apple’sSMAppServiceAPI and runs as root. You don’t manage this one — AAP does. - The AAP GUI (
App Auto-Patch.app) renders the dashboard, menu bar item, notifications, and Settings. It must run as the logged-in user, not as root, because:SMAppServicehelper approval is a per-user record. A root-launched GUI has no access to the user’s approval status, soHelperInstallationService.isOperationalreturnsfalseforever and the dashboard surfaces Setup Required — exactly what TechTrekkie’s screenshot showed.- The GUI needs an attached Aqua window server to render the menu bar item and post user notifications. The root context under launchd’s system domain has no window server.
- AAP’s UI-side state (
DiscoveredAppsData,FirstRunSetupCompleted,LastNotifiedPendingUpdatesCount, etc.) lives in~/Library/Preferences/. Running as root would persist these into/var/root/Library/Preferences/, where the real user can never see them. A LaunchAgent runs once per logged-in user, as that user, in their Aqua session — which is exactly the right context for the AAP GUI. Build 39 also logs a prominent[WARNING]line at startup whengetuid() == 0, so future LaunchDaemon-as-root deployments show up in the diagnostic bundle.
Deploying the LaunchAgent
Section titled “Deploying the LaunchAgent”File contents
Section titled “File contents”Use the sample at
Profile Manifests/xyz.techitout.appAutoPatch.agent.example.plist —
copy it to /Library/LaunchAgents/xyz.techitout.appAutoPatch.agent.plist
on every managed Mac. Key fields:
Label:xyz.techitout.appAutoPatch.agentProgramArguments:/Applications/App Auto-Patch.app/Contents/MacOS/App Auto-PatchRunAtLoad:trueKeepAlive→SuccessfulExit:false(re-spawn on crash, respect Cmd+Q from the user)LimitLoadToSessionType:AquaThrottleInterval:30(cap the relaunch rate so a wedged build can’t spin)StandardOutPath/StandardErrorPath: redirected into/Library/Application Support/AppAutoPatch/logs/for early-launch crash capture.EnvironmentVariables→AAP_LAUNCH_AGENT:1. Build 89 allows one GUI per user (#203). This variable tells that process it belongs to the LaunchAgent, so it replaces any copyopenor Finder already started. A plist deployed before build 89 needs this key added. Without it, a second launch still exits, and the copy that started first is the one that stays.
Open at login from Settings
Section titled “Open at login from Settings”Build 89 can write the login agent itself. The About tab has Open at
login and Keep running if quit. A configuration profile can set
OpenAtLogin and KeepAlive (both default false). Each toggle is
locked when that key is managed, and both are locked when LockSettings
is true.
KeepAlive starts App Auto-Patch again after the user quits it, and
also starts it at login. OpenAtLogin alone starts it at login and
still lets a quit stay quit.
The app writes
~/Library/LaunchAgents/xyz.techitout.appAutoPatch.login.plist.
If /Library/LaunchAgents/xyz.techitout.appAutoPatch.agent.plist is
already installed, the app does not write that second agent, and the
Settings toggles stay off. Deploy one or the other, not both.
Permissions
Section titled “Permissions”The agent plist must be owned by root:wheel with mode 0644. macOS
refuses to load LaunchAgents with looser permissions.
Deployment via Jamf
Section titled “Deployment via Jamf”- Build a
.pkgthat drops the file at/Library/LaunchAgents/xyz.techitout.appAutoPatch.agent.plistwith the right ownership and mode (e.g. viapkgbuild --rootfrom a prepared payload directory). - Sign the package with your developer certificate.
- Upload to Jamf and scope to the managed-Mac smart group.
- On the next login (or
sudo launchctl bootstrap gui/<uid> /Library/LaunchAgents/xyz.techitout.appAutoPatch.agent.plistfor an active session) the agent loads.
Deployment via Munki
Section titled “Deployment via Munki”- Build the same
.pkgand treat it as amanaged_installsitem. - Add a
requiresdependency on the AAP app package so the agent is never installed without the binary it points to.
Deployment via Intune
Section titled “Deployment via Intune”- Package the LaunchAgent plist into a
.pkg(Intune accepts shell scripts too; either works). - Assign to the managed user/device group.
Verifying after deployment
Section titled “Verifying after deployment”After the next user login, on a managed Mac:
sudo launchctl print gui/$(stat -f %u /dev/console)/xyz.techitout.appAutoPatch.agentYou should see the job listed, state running, and the path matching
the installed bundle. aap.log will contain a fresh
AppAutoPatch: Launched v4.0.0 (39); ... silentLaunch=true; uid=<user uid>
line — the uid field is what proves the GUI is running as the
logged-in user rather than as root.
Combining with ManagedSilentLaunchEnabled
Section titled “Combining with ManagedSilentLaunchEnabled”The LaunchAgent on its own would auto-launch AAP at login with a
visible Dock icon and dashboard window — useful for consumer Macs,
but not what managed fleets want.
Build 40 makes silent launch the managed-Mac default: if your
config profile sets any AAP-aware managed preference (e.g.
IgnoredLabels, DeferralTimerDefault, SupportTeamWebsite),
AAP automatically launches in .accessory activation policy and
auto-closes its initial dashboard window. No extra opt-in key is
required. From the user’s point of view AAP is invisible until
either:
- a pending update is found and
UpdatesAvailableNotifierposts a user notification, or - they click the menu bar item.
Both surfaces are unaffected by silent-launch mode.
If you specifically want managed Macs to keep the dashboard
visible on launch (e.g. for kiosk-style deployments), set
ManagedSilentLaunchEnabled = falsein the profile. Explicit values always win over the auto-default.
Migrating from a LaunchDaemon-as-root deployment
Section titled “Migrating from a LaunchDaemon-as-root deployment”If you previously deployed AAP via a system LaunchDaemon with
UserName = root (the original TechTrekkie configuration), do this on
the affected Macs:
# Stop and unload the broken root daemonsudo launchctl bootout system /Library/LaunchDaemons/xyz.zillow.appautopatch.plistsudo rm /Library/LaunchDaemons/xyz.zillow.appautopatch.plist
# Drop the new agent into place (assuming the file is already staged)sudo install -o root -g wheel -m 0644 \ /path/to/xyz.techitout.appAutoPatch.agent.plist \ /Library/LaunchAgents/xyz.techitout.appAutoPatch.agent.plist
# Bootstrap for the currently-logged-in user (or wait for next login)CONSOLE_USER_UID=$(stat -f %u /dev/console)sudo launchctl bootstrap gui/${CONSOLE_USER_UID} \ /Library/LaunchAgents/xyz.techitout.appAutoPatch.agent.plistAfter this the Setup Required state should clear (the helper was fine all along — only the GUI’s view of it was broken because it couldn’t read the user’s SMAppService approval record).
Caveats
Section titled “Caveats”LimitLoadToSessionType = Aquais required. Without it, launchd also tries to launch the agent in the LoginWindow and Background session types, which fail because the GUI requires an attached window server.StandardOutPath/StandardErrorPathwrite to/Library/Application Support/AppAutoPatch/logs/. That directory is ownedroot:staff 0775(per the build-32 #16 fix), so any local user can read and append. The actual AAP log (aap.log) remains the canonical source; the launchagent.stdout/stderr files are only relevant for crashes beforeLoggingServiceinitializes.- Cmd+Q is honored. The menu-bar popover’s Quit App Auto-Patch
exits the process cleanly, which is treated as a successful exit
and does not cause launchd to re-spawn AAP. The user has to
log out and back in (or run
launchctl kickstartmanually) to bring the agent back. This is intentional — when a user explicitly quits, we respect that. - The LaunchDaemon (root helper) is unaffected. It’s still managed by SMAppService, still runs as root, still does the privileged work. The agent only manages the GUI surface.
- One GUI per user (build 89, #203). The app package’s
postinstall used to
open -gjthe app even when this agent was also installed, which left two menu bar icons. From build 89, if this plist is already on disk, postinstall bootstraps and kickstarts the agent instead of opening a second copy. A second launch of the app exits. Redeploy the sample plist so it includesAAP_LAUNCH_AGENT=1; an agent that starts with that variable replaces the other copy. Quit any doubled processes once after upgrading. They do not merge on their own. - Settings can own the login agent instead.
OpenAtLoginandKeepAlivewrite a different plist,xyz.techitout.appAutoPatch.login, in the user’s LaunchAgents folder. Leave this organization plist undeployed when you use those keys.
Related docs
Section titled “Related docs”Profile Manifests/README.md— overview of managed-preference deployment (Jamf JSON schema vs. ProfileCreator/iMazing pfm format).Profile Manifests/MDM-ServiceManagement.md— how to pre-approve the root helper LaunchDaemon via acom.apple.servicemanagementpayload so users never see the requires approval state.Profile Manifests/MDM-Notifications.md— how to pre-allow (or fleet-wide suppress) macOS user notifications for AAP so the silent-launch path can surface pending updates without a permission prompt.
