App Policies
App Policies let administrators specify custom update behavior on a per-app basis. Each policy maps a catalog ID to a dictionary of rules that govern how — and whether — Alectrona Patch will update that specific app.
App Policies are configured via the AppPolicies key in the com.alectrona.patch MDM domain. They are typically deployed through a configuration profile and apply to all Patch invocations on the device (Patch Menu Bar app, Patch Agent, and the patch command line tool).
App Policies gate which catalog versions are eligible for installation; they cannot install older or specific historical versions. Patch always installs the latest catalog version that satisfies every rule in the policy. If no eligible version exists (e.g. every catalog version exceeds MaxUpdateVersion), the app is silently skipped — there is no end-user notification.
Supported Policy Keys​
App Policies support the following inner keys. All keys are optional; mix and match the rules you need for each app.
| Key | Type | Purpose |
|---|---|---|
MaxUpdateVersion | String | Skip catalog versions above the specified version. |
BlockedVersions | Array of strings | Skip specific catalog versions. |
RequireClosed | Bool | Require the app to be closed before updating. |
PreInstallScript | String (path) | Run this script before each install. |
PostInstallScript | String (path) | Run this script after each install. |
Common Use Cases​
- Cap an app at a known-good version
- Skip specific broken releases
- Require the app to be closed before updating
- Run pre-install / post-install scripts
- Combine rules for an app
"We've validated Slack 4.40 against our environment and don't want to roll out anything newer until we've tested it."
Use MaxUpdateVersion to set a ceiling. Patch will continue to update Slack to newer catalog versions up to 4.40, but skip any catalog version above 4.40 until you raise or remove the ceiling.
<!-- Domain: com.alectrona.patch -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AppPolicies</key>
<dict>
<key>slack</key>
<dict>
<key>MaxUpdateVersion</key>
<string>4.40</string>
</dict>
</dict>
</dict>
</plist>
"Google Chrome 120.0.6099.71 has a known regression that breaks our internal extension. Skip that exact version but keep updating to anything newer."
Use BlockedVersions to skip specific catalog releases. Patch will skip every version listed in the array and continue to update to any other eligible catalog version.
<!-- Domain: com.alectrona.patch -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AppPolicies</key>
<dict>
<key>google-chrome</key>
<dict>
<key>BlockedVersions</key>
<array>
<string>120.0.6099.71</string>
<string>120.0.6099.72</string>
</array>
</dict>
</dict>
</dict>
</plist>
"Adobe Acrobat doesn't tolerate in-place updates well — we want to update it only when the user has closed it."
Use RequireClosed to prevent Patch from updating an app while it's running. Patch will skip the update during the current cycle and retry on the next cycle when the app is no longer running.
<!-- Domain: com.alectrona.patch -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AppPolicies</key>
<dict>
<key>adobe-acrobat-reader</key>
<dict>
<key>RequireClosed</key>
<true/>
</dict>
</dict>
</dict>
</plist>
"We need to stop a license service before updating Microsoft Office, then restart it afterwards."
Use PreInstallScript and PostInstallScript to run setup and teardown logic around each managed update. Scripts run as root and their stdout / stderr are captured to the Patch log.
<!-- Domain: com.alectrona.patch -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AppPolicies</key>
<dict>
<key>microsoft-office</key>
<dict>
<key>PreInstallScript</key>
<string>/usr/local/bin/office-pre.sh</string>
<key>PostInstallScript</key>
<string>/usr/local/bin/office-post.sh</string>
</dict>
</dict>
</dict>
</plist>
"We want to pin Zoom to versions at or below 5.18, skip a specific broken release, and only update it when Zoom isn't open."
Multiple inner keys can be combined within a single policy. All rules must be satisfied for an update to proceed.
<!-- Domain: com.alectrona.patch -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AppPolicies</key>
<dict>
<key>zoom-us</key>
<dict>
<key>MaxUpdateVersion</key>
<string>5.18</string>
<key>BlockedVersions</key>
<array>
<string>5.17.5</string>
</array>
<key>RequireClosed</key>
<true/>
</dict>
</dict>
</dict>
</plist>
CLI Counterpart: --max-update-version​
The Patch CLI provides a --max-update-version flag on patch install --update-only that mirrors the MaxUpdateVersion ceiling for ad-hoc invocations. This is useful for scripted workflows or one-off updates where you want to constrain a particular run without modifying the managed AppPolicies configuration.
# Update Slack to the latest catalog version that is 4.40 or lower
# (catalog versions above 4.40 are skipped — this cannot install an older version)
sudo patch install slack --update-only --max-update-version 4.40
When both the --max-update-version CLI flag and an AppPolicies MaxUpdateVersion ceiling are configured for the same app, both apply independently and the most restrictive ceiling wins. For example, if AppPolicies.MaxUpdateVersion is 5.18 and you pass --max-update-version 5.17, Patch will use 5.17 for that invocation.
Visibility​
App Policies are silently enforced. Apps held back by a policy do not appear with a "held back" indicator in the Patch Menu Bar app, and the user receives no notification that an update was skipped — this is intentional, since users do not need to be aware of updates they were never going to receive.
Administrators can verify policies are being applied and observe skip decisions in the Patch log at /var/log/alectrona-patch.log. Skip decisions (blocked version match, ceiling exceeded, RequireClosed deferral while the app is running) are logged at info level. The full set of loaded policy rules for an app is logged at debug level, which is useful for confirming the deployed configuration profile is being read as intended.
See Also​
AppPoliciespreference referencepatch installcommand line tool