fix(clean): resolve bundle IDs via filesystem when Spotlight misses (closes #732, closes #733) (#735)
* fix(clean): resolve bundle IDs via filesystem when Spotlight misses (closes #732, closes #733)
`mo clean` flagged two classes of false positives:
- Potential stale login item: org.keepassxc.KeePassXC (issue #732)
- Orphaned service: /Library/PrivilegedHelperTools/com.adobe.ARMDC.* (issue #733)
Both call sites relied exclusively on
`mdfind "kMDItemCFBundleIdentifier == '<id>'"` to decide whether the
app that owns a plist/helper is installed. Spotlight is unreliable for
this: indexing is sometimes disabled on /Applications, Homebrew casks
occasionally skip metadata importers, and Spotlight does not index
privileged helpers that live embedded inside a parent .app under
`Contents/Library/LaunchServices/<id>` (the SMJobBless convention used
by Adobe, 1Password, Docker, etc.).
Introduce `bundle_has_installed_app` in a new
`lib/core/bundle_resolver.sh` module. It keeps mdfind as the fast path
and, on a miss, walks `/Applications`, `/Applications/Setapp`,
`/Applications/Utilities`, and `~/Applications`, reading each
`Info.plist` for `CFBundleIdentifier` and checking each app's
`Contents/Library/LaunchServices` directory for a registered helper
with the given ID. Results are cached in parallel indexed arrays for
the session (macOS bash 3.2 has no associative arrays).
- `hint_launch_agent_bundle_exists` now delegates to the resolver, so
homebrew-installed apps like KeePassXC resolve correctly.
- The generic fallback in `clean_orphaned_system_services` for
PrivilegedHelperTools uses the resolver, so helpers shipped inside a
parent .app (Adobe ARMDC, Acrobat CC Installer, etc.) are no longer
flagged when the parent app is installed.
Tests cover: app-bundle-ID lookup on a Spotlight miss, SMJobBless
helper lookup inside a parent app, negative lookup for a ghost bundle
ID, rejection of malformed IDs, and cache honouring within a session.
* refactor(core): drop session cache from bundle_resolver
The cache was premature optimization: in practice the resolver is
called a handful of times per \`mo clean\` run, and the slow path is
gated by a 2s mdfind timeout plus a bounded \`find -maxdepth 1\` over
four app roots. Removing the parallel-array cache, its get/put
helpers, and the reset-for-tests function drops the module from ~118
lines to ~70 and removes an implementation detail from the public
surface (no more \`mole_bundle_resolver_reset_cache\`).
No behavior change for callers. The resolver still does Spotlight →
filesystem fallback, still rejects malformed IDs, still handles both
app-owned bundles (#732) and SMJobBless helpers embedded in a parent
.app (#733).
Tests:
- Removed the caching-specific test (validated implementation, not
behavior).
- Kept the four functional tests covering Spotlight miss, SMJobBless
helper discovery, negative result, and malformed-ID rejection. S
Sebastian Breguel committed
6a055de434b1d9e6cb93ebf5284ad22c8b65dbbb
Parent: 3f94133
Committed by GitHub <noreply@github.com>
on 4/14/2026, 7:22:32 AM