> Help me add a feature. Munki should support a new manifest key named "optional_uninstalls". Items listed there are only
available for _uninstall_ and only appear in Managed Software Center if they are currently installed. This allows admins to
"phase out" software items -- they can move items from "optional_installs" to "optional_uninstalls" to prevent any new
installations of an item, but also allowing users to remove the items from their Macs if and when they want.
After some thinking, and a correction when it wanted to make changes to the Python files in munkilib, it came up with this plan:
Plan: Add optional_uninstalls manifest key to Munki (Swift only)
Context
Admins need a way to "phase out" software: prevent new installs while still letting users self-remove copies already on their
Macs. The new optional_uninstalls manifest key fills that gap. Items listed there:
- Only appear in Managed Software Center if they are currently installed
- Only offer Uninstall (never Install)
- Behave like optional_installs in every other way (self-serve via SelfServeManifest, catalogs, etc.)
---
Approach
1. Separate key in InstallInfo.plist — optional_uninstalls stored as its own top-level array. Keeps semantics clear and doesn't
touch the existing flow.
2. New processOptionalUninstall() function — mirrors processOptionalInstall() but only adds an item if it is currently installed,
and skips install-only paths (precache, disk-space note for not-installed, etc.).
3. MSC merges both lists — getOptionalInstallItems() reads both arrays from InstallInfo.plist, tagging optional_uninstalls items
with uninstall_only = true. Items tagged uninstall_only that are not installed are dropped at load time.
4. Status / button logic — for uninstall_only items the not-installed → install-requested transition in update_status() is
blocked.
5. mutuallyExclusiveSections — optional_uninstalls added so an item can't simultaneously be in optional_installs.
---
Files to change
code/cli/munki/shared/updatecheck/analyze.swift
- New async function processOptionalUninstall(_:catalogList:installInfo:):
- Mirrors processOptionalInstall() structure.
- After alreadyProcessed() check, also checks optional_uninstalls list for a duplicate name.
- Calls someVersionInstalled() — if not installed, logs and returns without adding anything.
- Skips the unusedSoftwareShouldBeRemoved / precache / disk-space-for-not-installed blocks.
- Builds the same processedItem dict (name, display_name, description, version_to_install, installed=true, uninstallable,
etc.).
- Appends to installInfo["optional_uninstalls"] instead of optional_installs.
code/cli/munki/shared/updatecheck/manifests.swift
- In processManifest(_:forKey:installInfo:parentCatalogs:manifestName:) (~line 386), inside the item loop: add if key ==
"optional_uninstalls" { _ = await processOptionalUninstall(item, catalogList: catalogList, installInfo: &installInfo) }.
code/cli/munki/shared/updatecheck/updatecheck.swift
- installInfo init dict (~line 297): add "optional_uninstalls": [PlistDict]().
- processLocalOnlyManifest() key list (~line 132): add "optional_uninstalls" to the iterated key array.
- After processManifest(... "optional_installs" ...) (~line 381): add a parallel call for "optional_uninstalls".
- processSelfServeManifest() (~line 213 — the will_be_removed update loop): add an identical loop over
installInfo["optional_uninstalls"] items to set will_be_removed when they appear in removals.
code/cli/munki/shared/installer/installer.swift
- Post-install status update (~line 735): add a parallel loop for installInfo["optional_uninstalls"] items. For each removal
result whose name matches, update removal_error / installed flags identically to the existing optional_installs loop. (No
install-result path needed since these items can never be installed via this key.)
code/apps/Managed Software Center/Managed Software Center/MunkiItems.swift
- optionalInstallsExist() (~line 1152): also check optional_uninstalls array non-empty (so the sidebar appears when only
phase-out items are present and installed — though in practice they'd be filtered by install state later, so it's cleaner to
check raw count here and let getOptionalInstallItems() filter).
- getOptionalInstallItems() (~line 1157):
- Also read cachedInstallInfo()["optional_uninstalls"] as? [[String:Any]].
- Filter to items where installed == true.
- Set uninstall_only = true on each before wrapping in OptionalItem.
- Append to the combined optional_install_items array before caching.
- OptionalItem._get_status() (~line 743): if uninstall_only == true and installed == false, return "" early (the item will be
excluded). The installed branch already produces correct removal-oriented statuses with no changes needed.
- OptionalItem.update_status() (~line 941): in the "not-installed" case, add a guard — if uninstall_only == true, do nothing and
return false (no install transition allowed).
code/cli/munki/manifestutil/MUmanifestEditing.swift
- validPkgSections (~line 77): add "optional_uninstalls".
- mutuallyExclusiveSections (~line 84): add "optional_uninstalls" so it can't coexist with managed_installs, managed_uninstalls,
or optional_installs for the same item.
code/cli/munki/manifestutil/MUrunInteractive.swift
- sections array (~line 56): add "optional_uninstalls" for tab completion.
code/cli/munki/repoclean/repoclean.swift
- Lines 145 and 156: add "optional_uninstalls" to the manifest section key arrays that repoclean scans.
---
Verification
1. Existing tests — run analyzeTests.swift and the full test suite to confirm no regressions.
2. Manual end-to-end:
- Item in optional_installs — confirm unchanged behavior.
- Item moved to optional_uninstalls on a machine where it is not installed → should not appear in MSC at all.
- Same item on a machine where it is installed → appears in MSC with only a "Remove" button; clicking it writes to
SelfServeManifest; next managedsoftwareupdate run removes it; item disappears from MSC afterward.
3. manifestutil — manifestutil --add-pkg <item> --section optional_uninstalls <manifest> accepted; same item rejected if already
in optional_installs.
4. repoclean — runs without error against a repo whose manifests contain optional_uninstalls.
This seems a reasonable plan. Does this feature sound interesting/reasonable? Would you use it? (Pretty sure I would)
This is something I’ve wanted for a while and just never got around to thinking about it hard enough to start implementing it. AI code assist now makes it much easier to get started with actually implementing it...
-Greg
--
Find related discussion groups here:
https://github.com/munki/munki/wiki/Discussion-Group
---
You received this message because you are subscribed to the Google Groups "munki-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to munki-dev+...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/munki-dev/E27F9FDC-E361-4939-A469-3DD713E4FECB%40mac.com.
To view this discussion visit https://groups.google.com/d/msgid/munki-dev/4CD2EB79-90F9-4B0E-B6E9-74864FCFEF34%40mac.com.
To view this discussion visit https://groups.google.com/d/msgid/munki-dev/CAJySx-m210P55eQvOVQyTi5UNZZ%2BfZeKA8SNacgA2AXY9vtWPQ%40mail.gmail.com.
On Aug 21, 2026, at 6:57 AM, Nick McSpadden <nmcsp...@gmail.com> wrote:
I would like to have some very explicit warning in MSC (maybe in red text or something) that clearly lays out that removing this item means you won't be able to find it again.
To view this discussion visit https://groups.google.com/d/msgid/munki-dev/E7BF4F00-D2C2-4E58-82A1-6D85BFAF5F88%40mac.com.
To view this discussion visit https://groups.google.com/d/msgid/munki-dev/CAJySx-mb9b-iELrX1%3D5mQeeuZLmq4qcSDn%2BAhg9VyEmRfF18tQ%40mail.gmail.com.
To view this discussion visit https://groups.google.com/d/msgid/munki-dev/DB8611CE-F3C6-4C26-84E7-8C507A91C5A9%40mac.com.
To view this discussion visit https://groups.google.com/d/msgid/munki-dev/CAJySx-nHNVLN7%3DZefcoyv5H4Y8RUOnqFOek%2BaH%3DxNYUBSKo%3D%3DQ%40mail.gmail.com.
To view this discussion visit https://groups.google.com/d/msgid/munki-dev/CAJySx-nHNVLN7%3DZefcoyv5H4Y8RUOnqFOek%2BaH%3DxNYUBSKo%3D%3DQ%40mail.gmail.com.
To view this discussion visit https://groups.google.com/d/msgid/munki-dev/2ECCDDA5-4B9A-425A-BE0E-28F5B875F2CB%40mac.com.

To view this discussion visit https://groups.google.com/d/msgid/munki-dev/CAJySx-nHNVLN7%3DZefcoyv5H4Y8RUOnqFOek%2BaH%3DxNYUBSKo%3D%3DQ%40mail.gmail.com.
To view this discussion visit https://groups.google.com/d/msgid/munki-dev/9097EBBB-3091-445F-87DB-BA78C1C24011%40mac.com.
Agreed - I think putting it in the Updates tab makes the most sense.Would this display a notification to the user, or would users find this organically when visiting MSC for something else, like a pending update?
On Thu, Aug 20, 2026 at 8:52 PM 'Gregory Neagle' via munki-dev <munk...@googlegroups.com> wrote:
A mockup of one possibilty. This is similar in concept to “Other updates”, in which there are items that are installed but not in the SelfServeManifest, and there’s a newer version in optional_installs.
To view this discussion visit https://groups.google.com/d/msgid/munki-dev/CAN1%2Bh_ZmvH5%3DuZdf%3DXtnDV0UXqmb9q3c6j3jpWQ4iBDG9j%3DHgA%40mail.gmail.com.
Foo is no longer recommended or supported. Please consider removing it from your Mac. Possible replacement applications include Bar and Baz. Contact Help Desk if you need additional information.
To view this discussion visit https://groups.google.com/d/msgid/munki-dev/a2dc6cd4-193b-4812-8223-ee1a3aee78d1n%40googlegroups.com.


To view this discussion visit https://groups.google.com/d/msgid/munki-dev/817EC79D-ED02-4541-8EB8-6F8D11F21838%40mac.com.
On Aug 21, 2026, at 5:13 PM, 'Gregory Neagle' via munki-dev <munk...@googlegroups.com> wrote:
Working today with Claude, here’s where I am.I opted for an explicit “optional_uninstalls” key. As you’ll see below, it might make sense to change that key name to “suggested_uninstalls”.For this demo, I added “VirtualBuddy” to optional_uninstalls for my machine’s manifest:
<PastedGraphic-1.png>The new “Suggested removals” section acts very much like the “Other available updates” section that appears when there are optional installs, currently installed, but not in the SelfServeManifest (perhaps the items were manually installed, or migrated from another Mac). Clicking “Remove” on an item in this section adds the item to managed_uninstalls in the SelfServeManifest and triggers an update check. Once the update check is complete, the item appears under “Pending updates”:
To view this discussion visit https://groups.google.com/d/msgid/munki-dev/30C10B57-3C4F-4A44-8242-C7654D2C049F%40mac.com.