Skip to content

Update Fleet-maintained apps#48597

Closed
fleet-release wants to merge 1 commit into
mainfrom
fma-2607012031
Closed

Update Fleet-maintained apps#48597
fleet-release wants to merge 1 commit into
mainfrom
fma-2607012031

Conversation

@fleet-release

@fleet-release fleet-release commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Automated ingestion of latest Fleet-maintained app data.

Summary by CodeRabbit

  • Bug Fixes

    • Improved macOS uninstall behavior across many apps so background services are removed more reliably, including when service names use wildcard patterns.
    • Updated multiple app/package entries to point to newer installer or uninstall metadata versions.
    • Refreshed several app version records and checksums to match the latest releases.
  • Refactor

    • Standardized uninstall handling across the maintained app catalog for more consistent cleanup during removal.

Generated automatically with cmd/maintained-apps.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

This PR updates uninstall_script_ref values and replaces embedded uninstall bash scripts across roughly 45 maintained-apps darwin.json manifests. The primary code change is a remove_launchctl_service enhancement that expands wildcard (*) service label patterns into currently loaded launchctl job labels via regex matching before removing services and associated LaunchAgent/LaunchDaemon plist files. Separately, several unrelated app manifests (chatwise, claude, dataflare, electronmail, filebeat, github, granola, jasp, dfu-blaster-pro) received version, installer URL, and sha256 checksum bumps.

Changes

Cohort Description
Wildcard launchctl removal ~45 apps updated with new uninstall_script_ref and script content adding wildcard-aware remove_launchctl_service
Version bumps ~11 manifests updated with new version, installer_url, sha256, and query thresholds

Sequence Diagram(s)

sequenceDiagram
  participant UninstallScript
  participant remove_launchctl_service
  participant launchctl

  UninstallScript->>remove_launchctl_service: call with service label (may contain "*")
  remove_launchctl_service->>remove_launchctl_service: build anchored regex from pattern
  remove_launchctl_service->>launchctl: list loaded jobs
  launchctl-->>remove_launchctl_service: job labels
  remove_launchctl_service->>remove_launchctl_service: match labels against regex
  remove_launchctl_service->>launchctl: remove matched service(s)
  remove_launchctl_service->>remove_launchctl_service: delete associated plist files
Loading

Related issues

None found.

Possibly related PRs

  • fleetdm/fleet#47053: Introduces the original dfu-blaster-pro darwin.json uninstall script that this PR's uninstall_script_ref update (c1079c00e818d04f) directly replaces.

Suggested labels: maintained-apps, documentation

Suggested reviewers: None determined from available context.

Poem
A rabbit hopped through scripts galore,
Wildcards tamed at every door.
Launchctl jobs, once single-named,
Now matched by patterns, neatly claimed.
Versions bumped, checksums anew—
A burrow full of JSON, through and through! 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is far too minimal and omits the template's required issue, checklist, testing, and other sections. Fill in the template sections, including Related issue, checklist items, testing, and any applicable migration or fleetd/orbit notes.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly states the PR updates Fleet-maintained apps and matches the change set.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fma-2607012031

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ee/maintained-apps/outputs/aviatrix-vpn-client/darwin.json`:
- Line 20: The launchctl cleanup input already includes the .plist suffix, but
remove_launchctl_service appends .plist again when building the
LaunchAgents/LaunchDaemons paths, causing .plist.plist targets. Update the call
site or the path construction in remove_launchctl_service so service labels are
handled consistently, and use the existing symbols remove_launchctl_service and
the aviatrix.vpn.client.rp.plist invocation to locate the fix.

In `@ee/maintained-apps/outputs/box-drive/darwin.json`:
- Line 19: The wildcard package expansion in expand_pkgid_and_map is matching
package prefixes as a regex instead of literally, so dotted PKGID prefixes can
overmatch unrelated receipts. Update the receipt selection logic used by
expand_pkgid_and_map/forget_pkg/remove_pkg_files so the prefix is treated as a
literal string and only the intended com.box.desktop.installer.* receipts are
processed. Keep wildcard behavior for the trailing asterisk, but escape the
non-wildcard characters before comparing against pkgutil --pkgs output.

In `@ee/maintained-apps/outputs/chrome-remote-desktop-host/darwin.json`:
- Line 11: The shared uninstall template is skipping plist cleanup when wildcard
expansion via `launchctl list` finds no loaded jobs, so unloaded matching
LaunchAgents/Daemons can be left behind. Update the uninstall logic around the
wildcard handling in the shared template to also perform plist cleanup for the
original wildcard pattern, either by globbing matching plist basenames or by
keeping a separate cleanup path when no loaded job labels are returned, then
regenerate the affected manifest that references `uninstall_script_ref`.

In `@ee/maintained-apps/outputs/devin-desktop/darwin.json`:
- Line 19: The wildcard cleanup in remove_launchctl_service returns too early
when launchctl has no loaded matches, leaving installed but unloaded
LaunchAgent/LaunchDaemon plist files behind. Update the service expansion logic
to scan the LaunchAgents and LaunchDaemons plist paths for matching basenames
before deciding whether to return, then merge those results into services and
de-duplicate them so wildcard uninstall still removes unloaded plists.

In `@ee/maintained-apps/outputs/displaylink/darwin.json`:
- Line 20: The wildcard expansion in expand_pkgid_and_map is using regex
matching for package IDs, so a pattern like com.displaylink.* can accidentally
match unrelated receipts. Update the prefix-matching logic in
expand_pkgid_and_map and its callers like remove_pkg_files/forget_pkg to use a
fixed-string prefix check instead of grep regex behavior. Keep the wildcard
expansion behavior, but only select receipts that truly start with the computed
prefix.

In `@ee/maintained-apps/outputs/elgato-wave-link/darwin.json`:
- Line 20: The uninstall script is calling quit_application with a wildcard
bundle ID, but osascript only accepts an exact application ID. Update the
quit_application invocation to pass the real bundle identifier used by Elgato
Wave Link, and keep the matching logic in quit_application limited to exact IDs
so the app is reliably quit before removal.

In `@ee/maintained-apps/outputs/jetbrains-toolbox/darwin.json`:
- Line 19: The wildcard handling in remove_launchctl_service currently returns
early when launchctl list finds no loaded matches, which prevents cleanup of
matching LaunchAgent/LaunchDaemon plist files for unloaded jobs. Update the
wildcard branch to retain the original pattern as a cleanup candidate or to
enumerate matching plist basenames before the no-match return, so the later
plist removal logic still runs. Make the fix in the maintained-apps
template/generator that produces this JetBrains Toolbox output, then regenerate
the output.
- Line 19: The uninstall script is removing user-library parent directories with
a literal tilde, so the paths are not being expanded and the removals happen
before the related child items are trashed. Update the JetBrains Toolbox cleanup
sequence in darwin.json by moving the parent rmdir cleanup after the trash
calls, and change those targets to use the resolved user home path via
LOGGED_IN_USER instead of '~/...'. Refer to the uninstall flow around
remove_launchctl_service, send_signal, trash, and the subsequent sudo rmdir
calls.

In `@ee/maintained-apps/outputs/microsoft-auto-update/darwin.json`:
- Line 19: The cleanup calls in the Microsoft AutoUpdate script are using a
literal tilde path, so the user cache directories are not actually removed.
Update the direct removal logic in the script that defines LOGGED_IN_USER and
the trailing cleanup calls so they resolve to /Users/$LOGGED_IN_USER/... instead
of using '~', and keep the more specific uls directory removed before its parent
Microsoft cache folder.

In `@ee/maintained-apps/outputs/microsoft-edge/darwin.json`:
- Line 19: The parent directory removals in the Edge cleanup script are using
quoted tilde paths, so `sudo rmdir` treats them literally and runs before the
child cleanup work. Update the cleanup flow around the
`remove_launchctl_service`/`trash` calls so those parent directories are removed
only after their contents are trashed, and change the `rmdir` targets to use the
resolved `/Users/$LOGGED_IN_USER/...` form instead of quoted `~` paths.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 24607bdb-51a5-4609-99c4-b47848e0a267

📥 Commits

Reviewing files that changed from the base of the PR and between 4076d13 and 637b4e2.

📒 Files selected for processing (128)
  • ee/maintained-apps/outputs/adguard/darwin.json
  • ee/maintained-apps/outputs/adlock/darwin.json
  • ee/maintained-apps/outputs/adobe-acrobat-reader/darwin.json
  • ee/maintained-apps/outputs/aldente/darwin.json
  • ee/maintained-apps/outputs/amazon-workspaces/darwin.json
  • ee/maintained-apps/outputs/anka-virtualization/darwin.json
  • ee/maintained-apps/outputs/appcleaner/darwin.json
  • ee/maintained-apps/outputs/aviatrix-vpn-client/darwin.json
  • ee/maintained-apps/outputs/aws-vpn-client/darwin.json
  • ee/maintained-apps/outputs/background-music/darwin.json
  • ee/maintained-apps/outputs/bartender/darwin.json
  • ee/maintained-apps/outputs/boom-3d/darwin.json
  • ee/maintained-apps/outputs/box-drive/darwin.json
  • ee/maintained-apps/outputs/breaktimer/darwin.json
  • ee/maintained-apps/outputs/cardhop/darwin.json
  • ee/maintained-apps/outputs/charles/darwin.json
  • ee/maintained-apps/outputs/chatwise/darwin.json
  • ee/maintained-apps/outputs/chrome-remote-desktop-host/darwin.json
  • ee/maintained-apps/outputs/citrix-workspace/darwin.json
  • ee/maintained-apps/outputs/claude/darwin.json
  • ee/maintained-apps/outputs/clocker/darwin.json
  • ee/maintained-apps/outputs/cloudflare-warp/darwin.json
  • ee/maintained-apps/outputs/coconutbattery/darwin.json
  • ee/maintained-apps/outputs/crashplan/darwin.json
  • ee/maintained-apps/outputs/daisydisk/darwin.json
  • ee/maintained-apps/outputs/dataflare/darwin.json
  • ee/maintained-apps/outputs/dataflare/windows.json
  • ee/maintained-apps/outputs/debookee/darwin.json
  • ee/maintained-apps/outputs/devin-desktop/darwin.json
  • ee/maintained-apps/outputs/dfu-blaster-pro/darwin.json
  • ee/maintained-apps/outputs/displaylink/darwin.json
  • ee/maintained-apps/outputs/docker-desktop/darwin.json
  • ee/maintained-apps/outputs/dropbox/darwin.json
  • ee/maintained-apps/outputs/druva-insync/darwin.json
  • ee/maintained-apps/outputs/duo-desktop/darwin.json
  • ee/maintained-apps/outputs/dymo-connect/darwin.json
  • ee/maintained-apps/outputs/electronmail/darwin.json
  • ee/maintained-apps/outputs/elgato-camera-hub/darwin.json
  • ee/maintained-apps/outputs/elgato-wave-link/darwin.json
  • ee/maintained-apps/outputs/expressvpn/darwin.json
  • ee/maintained-apps/outputs/fig/darwin.json
  • ee/maintained-apps/outputs/filebeat/windows.json
  • ee/maintained-apps/outputs/fing/darwin.json
  • ee/maintained-apps/outputs/focusrite-control-2/darwin.json
  • ee/maintained-apps/outputs/forklift/darwin.json
  • ee/maintained-apps/outputs/free-download-manager/darwin.json
  • ee/maintained-apps/outputs/fsmonitor/darwin.json
  • ee/maintained-apps/outputs/gitfinder/darwin.json
  • ee/maintained-apps/outputs/github/darwin.json
  • ee/maintained-apps/outputs/gog-galaxy/darwin.json
  • ee/maintained-apps/outputs/google-chrome/darwin.json
  • ee/maintained-apps/outputs/google-drive/darwin.json
  • ee/maintained-apps/outputs/google-earth-pro/darwin.json
  • ee/maintained-apps/outputs/google-gemini/darwin.json
  • ee/maintained-apps/outputs/granola/darwin.json
  • ee/maintained-apps/outputs/granola/windows.json
  • ee/maintained-apps/outputs/gyazo/darwin.json
  • ee/maintained-apps/outputs/hazeover/darwin.json
  • ee/maintained-apps/outputs/hiddenbar/darwin.json
  • ee/maintained-apps/outputs/i1profiler/darwin.json
  • ee/maintained-apps/outputs/jasp/darwin.json
  • ee/maintained-apps/outputs/jetbrains-toolbox/darwin.json
  • ee/maintained-apps/outputs/klokki/darwin.json
  • ee/maintained-apps/outputs/malwarebytes/darwin.json
  • ee/maintained-apps/outputs/megasync/darwin.json
  • ee/maintained-apps/outputs/microsoft-auto-update/darwin.json
  • ee/maintained-apps/outputs/microsoft-edge/darwin.json
  • ee/maintained-apps/outputs/microsoft-excel/darwin.json
  • ee/maintained-apps/outputs/microsoft-outlook/darwin.json
  • ee/maintained-apps/outputs/microsoft-powerpoint/darwin.json
  • ee/maintained-apps/outputs/microsoft-teams/darwin.json
  • ee/maintained-apps/outputs/mist/darwin.json
  • ee/maintained-apps/outputs/mullvad-vpn/darwin.json
  • ee/maintained-apps/outputs/ndi-tools/darwin.json
  • ee/maintained-apps/outputs/nextcloud/darwin.json
  • ee/maintained-apps/outputs/nordlayer/darwin.json
  • ee/maintained-apps/outputs/nordpass/darwin.json
  • ee/maintained-apps/outputs/nordvpn/darwin.json
  • ee/maintained-apps/outputs/numi/darwin.json
  • ee/maintained-apps/outputs/okta-verify/darwin.json
  • ee/maintained-apps/outputs/omnissa-horizon-client/darwin.json
  • ee/maintained-apps/outputs/onedrive/darwin.json
  • ee/maintained-apps/outputs/opencode-desktop/darwin.json
  • ee/maintained-apps/outputs/openvpn-connect/darwin.json
  • ee/maintained-apps/outputs/plex-media-server/darwin.json
  • ee/maintained-apps/outputs/postgres-app/darwin.json
  • ee/maintained-apps/outputs/power-monitor/darwin.json
  • ee/maintained-apps/outputs/pritunl/darwin.json
  • ee/maintained-apps/outputs/privileges/darwin.json
  • ee/maintained-apps/outputs/proton-mail-bridge/darwin.json
  • ee/maintained-apps/outputs/protonvpn/darwin.json
  • ee/maintained-apps/outputs/proxyman/darwin.json
  • ee/maintained-apps/outputs/radio-silence/darwin.json
  • ee/maintained-apps/outputs/santa/darwin.json
  • ee/maintained-apps/outputs/sensei/darwin.json
  • ee/maintained-apps/outputs/sharefile/darwin.json
  • ee/maintained-apps/outputs/shifty/darwin.json
  • ee/maintained-apps/outputs/signal/windows.json
  • ee/maintained-apps/outputs/sound-control/darwin.json
  • ee/maintained-apps/outputs/sourcetree/darwin.json
  • ee/maintained-apps/outputs/splashtop-business/darwin.json
  • ee/maintained-apps/outputs/splashtop-streamer/darwin.json
  • ee/maintained-apps/outputs/stats/darwin.json
  • ee/maintained-apps/outputs/steam/darwin.json
  • ee/maintained-apps/outputs/surge/darwin.json
  • ee/maintained-apps/outputs/teamviewer/darwin.json
  • ee/maintained-apps/outputs/tiles/darwin.json
  • ee/maintained-apps/outputs/tripmode/darwin.json
  • ee/maintained-apps/outputs/tunnelblick/darwin.json
  • ee/maintained-apps/outputs/tuple/darwin.json
  • ee/maintained-apps/outputs/twingate/darwin.json
  • ee/maintained-apps/outputs/ua-connect/darwin.json
  • ee/maintained-apps/outputs/unity-hub/darwin.json
  • ee/maintained-apps/outputs/viscosity/darwin.json
  • ee/maintained-apps/outputs/visual-studio-code/darwin.json
  • ee/maintained-apps/outputs/vnc-server/darwin.json
  • ee/maintained-apps/outputs/vyprvpn/darwin.json
  • ee/maintained-apps/outputs/whatroute/darwin.json
  • ee/maintained-apps/outputs/wifiman/darwin.json
  • ee/maintained-apps/outputs/windows-app/darwin.json
  • ee/maintained-apps/outputs/wireshark-app/darwin.json
  • ee/maintained-apps/outputs/workflowy/darwin.json
  • ee/maintained-apps/outputs/xcreds/darwin.json
  • ee/maintained-apps/outputs/xquartz/darwin.json
  • ee/maintained-apps/outputs/zed/darwin.json
  • ee/maintained-apps/outputs/zed/windows.json
  • ee/maintained-apps/outputs/zoom-rooms/darwin.json
  • ee/maintained-apps/outputs/zoom/darwin.json

"4ad80bc8": "#!/bin/bash\n\n# variables\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\nexpand_pkgid_and_map() {\n local PKGID=\"$1\"\n local FUNC=\"$2\"\n if [[ \"$PKGID\" == *\"*\" ]]; then\n local prefix=\"${PKGID%\\*}\"\n echo \"Expanding wildcard for PKGID: $PKGID\"\n for receipt in $(pkgutil --pkgs | grep \"^${prefix}\"); do\n echo \"Processing $receipt\"\n \"$FUNC\" \"$receipt\"\n done\n else\n \"$FUNC\" \"$PKGID\"\n fi\n}\n\nforget_pkg() {\n local PKGID=\"$1\"\n expand_pkgid_and_map \"$PKGID\" forget_receipt\n}\n\nforget_receipt() {\n local PKGID=\"$1\"\n sudo pkgutil --forget \"$PKGID\"\n}\n\nremove_launchctl_service() {\n local service=\"$1\"\n local booleans=(\"true\" \"false\")\n local plist_status\n local paths\n local should_sudo\n\n echo \"Removing launchctl service ${service}\"\n\n for should_sudo in \"${booleans[@]}\"; do\n plist_status=$(launchctl list \"${service}\" 2>/dev/null)\n\n if [[ $plist_status == \\{* ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo launchctl remove \"${service}\"\n else\n launchctl remove \"${service}\"\n fi\n sleep 1\n fi\n\n paths=(\n \"/Library/LaunchAgents/${service}.plist\"\n \"/Library/LaunchDaemons/${service}.plist\"\n )\n\n # if not using sudo, prepend the home directory to the paths\n if [[ $should_sudo == \"false\" ]]; then\n for i in \"${!paths[@]}\"; do\n paths[i]=\"${HOME}${paths[i]}\"\n done\n fi\n\n for path in \"${paths[@]}\"; do\n if [[ -e \"$path\" ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo rm -f -- \"$path\"\n else\n rm -f -- \"$path\"\n fi\n fi\n done\n done\n}\n\nremove_pkg_files() {\n local PKGID=\"$1\"\n expand_pkgid_and_map \"$PKGID\" remove_receipt_files\n}\n\nremove_receipt_files() {\n local PKGID=\"$1\"\n local PKGINFO VOLUME INSTALL_LOCATION FULL_INSTALL_LOCATION\n\n echo \"pkgutil --pkg-info-plist \\\"$PKGID\\\"\"\n PKGINFO=$(pkgutil --pkg-info-plist \"$PKGID\")\n VOLUME=$(echo \"$PKGINFO\" | awk '/<key>volume<\\/key>/ {getline; gsub(/.*<string>|<\\/string>.*/, \"\"); print}')\n INSTALL_LOCATION=$(echo \"$PKGINFO\" | awk '/<key>install-location<\\/key>/ {getline; gsub(/.*<string>|<\\/string>.*/, \"\"); print}')\n\n if [ -z \"$INSTALL_LOCATION\" ] || [ \"$INSTALL_LOCATION\" = \"/\" ]; then\n FULL_INSTALL_LOCATION=\"$VOLUME\"\n else\n FULL_INSTALL_LOCATION=\"$VOLUME/$INSTALL_LOCATION\"\n FULL_INSTALL_LOCATION=$(echo \"$FULL_INSTALL_LOCATION\" | sed 's|//|/|g')\n fi\n\n echo \"sudo pkgutil --only-files --files \\\"$PKGID\\\" | sed \\\"s|^|${FULL_INSTALL_LOCATION}/|\\\" | tr '\\\\\\\\n' '\\\\\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\"\n sudo pkgutil --only-files --files \"$PKGID\" | sed \"s|^|/${INSTALL_LOCATION}/|\" | tr '\\n' '\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\n\n echo \"sudo pkgutil --only-dirs --files \\\"$PKGID\\\" | sed \\\"s|^|${FULL_INSTALL_LOCATION}/|\\\" | grep '\\\\.app$' | tr '\\\\\\\\n' '\\\\\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\"\n sudo pkgutil --only-dirs --files \"$PKGID\" | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" | grep '\\.app$' | tr '\\n' '\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\n\n root_app_dir=$(\n sudo pkgutil --only-dirs --files \"$PKGID\" \\\n | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" \\\n | grep 'Applications' \\\n | awk '{ print length, $0 }' \\\n | sort -n \\\n | head -n1 \\\n | cut -d' ' -f2-\n )\n if [ -n \"$root_app_dir\" ]; then\n echo \"sudo rmdir -p \\\"$root_app_dir\\\" 2>/dev/null || :\"\n sudo rmdir -p \"$root_app_dir\" 2>/dev/null || :\n fi\n}\n\ntrash() {\n local logged_in_user=\"$1\"\n local target_file=\"$2\"\n local timestamp=\"$(date +%Y-%m-%d-%s)\"\n local rand=\"$(jot -r 1 0 99999)\"\n\n # replace ~ with /Users/$logged_in_user\n if [[ \"$target_file\" == ~* ]]; then\n target_file=\"/Users/$logged_in_user${target_file:1}\"\n fi\n\n local trash=\"/Users/$logged_in_user/.Trash\"\n\n # If the target contains glob characters, expand it and move each match.\n if [[ \"$target_file\" == *[*?[]* ]]; then\n local file file_name\n local matched=false\n local i=0\n # compgen -G expands the (quoted) pattern itself, so paths containing\n # spaces glob correctly; reading line by line keeps each match intact.\n while IFS= read -r file; do\n [[ -n \"$file\" ]] || continue\n [[ -e \"$file\" || -L \"$file\" ]] || continue\n matched=true\n i=$((i + 1))\n file_name=\"$(basename \"$file\")\"\n echo \"removing $file.\"\n # The per-match counter keeps matches that share a basename from\n # overwriting each other in the trash.\n mv -f \"$file\" \"$trash/${file_name}_${timestamp}_${rand}_${i}\"\n done < <(compgen -G \"$target_file\" 2>/dev/null)\n if [[ \"$matched\" == false ]]; then\n echo \"$target_file doesn't exist.\"\n fi\n return\n fi\n\n local file_name=\"$(basename \"${target_file}\")\"\n\n if [[ -e \"$target_file\" ]]; then\n echo \"removing $target_file.\"\n mv -f \"$target_file\" \"$trash/${file_name}_${timestamp}_${rand}\"\n else\n echo \"$target_file doesn't exist.\"\n fi\n}\n\nremove_launchctl_service 'aviatrix.vpn.client.rp.plist'\nremove_pkg_files 'com.Aviatrix.VPNClient'\nforget_pkg 'com.Aviatrix.VPNClient'\nsudo rm -rf '/Applications/Aviatrix VPN Client.app'\ntrash $LOGGED_IN_USER '~/Library/Aviatrix'\ntrash $LOGGED_IN_USER '~/Library/Logs/AviatrixVPNC'\ntrash $LOGGED_IN_USER '~/Library/Preferences/org.pythonmac.unspecified.AviatrixVPNClient.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/org.pythonmac.unspecified.AviatrixVPNClient.savedState'\n",
"717ae1f5": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nTMPDIR=$(dirname \"$(realpath \"$INSTALLER_PATH\")\")\n# functions\n\nquit_and_track_application() {\n local bundle_id=\"$1\"\n local var_name=\"APP_WAS_RUNNING_$(echo \"$bundle_id\" | tr '.-' '__')\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e \"application id \\\"$bundle_id\\\" is running\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n eval \"export $var_name=0\"\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n eval \"export $var_name=0\"\n return\n fi\n\n # App was running, mark it for relaunch\n eval \"export $var_name=1\"\n echo \"Application '$bundle_id' was running; will relaunch after installation.\"\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" >/dev/null 2>&1; then\n if ! pgrep -f \"$bundle_id\" >/dev/null 2>&1; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n fi\n sleep 1\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n fi\n}\n\n\nrelaunch_application() {\n local bundle_id=\"$1\"\n local var_name=\"APP_WAS_RUNNING_$(echo \"$bundle_id\" | tr '.-' '__')\"\n local was_running\n\n # Check if the app was running before installation\n eval \"was_running=\\$$var_name\"\n if [[ \"$was_running\" != \"1\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping relaunching application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Relaunching application '$bundle_id'...\"\n\n # Launch the app in the logged-in user's GUI session. Apps launched by root\n # won't register with the user's Dock/GUI, so run 'open' as the console user.\n # Use 'launchctl asuser' to bootstrap into the console user's Mach namespace\n # and GUI session — 'sudo -u' alone doesn't do this, which can cause\n # LSOpenURLsWithRole() failures even when 'open' exits 0.\n local open_status=0\n if [[ $EUID -eq 0 ]]; then\n local console_uid\n console_uid=$(id -u \"$console_user\")\n /bin/launchctl asuser \"$console_uid\" sudo -u \"$console_user\" open -b \"$bundle_id\" >/dev/null 2>&1 || open_status=$?\n else\n open -b \"$bundle_id\" >/dev/null 2>&1 || open_status=$?\n fi\n\n if [[ $open_status -eq 0 ]]; then\n echo \"Application '$bundle_id' relaunched successfully.\"\n else\n echo \"Failed to relaunch application '$bundle_id'.\"\n fi\n}\n\n\n# install pkg files\nquit_and_track_application 'org.pythonmac.unspecified.AviatrixVPNClient'\nsudo installer -pkg \"$TMPDIR/AVPNC_mac.pkg\" -target /\nrelaunch_application 'org.pythonmac.unspecified.AviatrixVPNClient'\n"
"717ae1f5": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nTMPDIR=$(dirname \"$(realpath \"$INSTALLER_PATH\")\")\n# functions\n\nquit_and_track_application() {\n local bundle_id=\"$1\"\n local var_name=\"APP_WAS_RUNNING_$(echo \"$bundle_id\" | tr '.-' '__')\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e \"application id \\\"$bundle_id\\\" is running\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n eval \"export $var_name=0\"\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n eval \"export $var_name=0\"\n return\n fi\n\n # App was running, mark it for relaunch\n eval \"export $var_name=1\"\n echo \"Application '$bundle_id' was running; will relaunch after installation.\"\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" >/dev/null 2>&1; then\n if ! pgrep -f \"$bundle_id\" >/dev/null 2>&1; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n fi\n sleep 1\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n fi\n}\n\n\nrelaunch_application() {\n local bundle_id=\"$1\"\n local var_name=\"APP_WAS_RUNNING_$(echo \"$bundle_id\" | tr '.-' '__')\"\n local was_running\n\n # Check if the app was running before installation\n eval \"was_running=\\$$var_name\"\n if [[ \"$was_running\" != \"1\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping relaunching application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Relaunching application '$bundle_id'...\"\n\n # Launch the app in the logged-in user's GUI session. Apps launched by root\n # won't register with the user's Dock/GUI, so run 'open' as the console user.\n # Use 'launchctl asuser' to bootstrap into the console user's Mach namespace\n # and GUI session — 'sudo -u' alone doesn't do this, which can cause\n # LSOpenURLsWithRole() failures even when 'open' exits 0.\n local open_status=0\n if [[ $EUID -eq 0 ]]; then\n local console_uid\n console_uid=$(id -u \"$console_user\")\n /bin/launchctl asuser \"$console_uid\" sudo -u \"$console_user\" open -b \"$bundle_id\" >/dev/null 2>&1 || open_status=$?\n else\n open -b \"$bundle_id\" >/dev/null 2>&1 || open_status=$?\n fi\n\n if [[ $open_status -eq 0 ]]; then\n echo \"Application '$bundle_id' relaunched successfully.\"\n else\n echo \"Failed to relaunch application '$bundle_id'.\"\n fi\n}\n\n\n# install pkg files\nquit_and_track_application 'org.pythonmac.unspecified.AviatrixVPNClient'\nsudo installer -pkg \"$TMPDIR/AVPNC_mac.pkg\" -target /\nrelaunch_application 'org.pythonmac.unspecified.AviatrixVPNClient'\n",
"7ef6154e": "#!/bin/bash\n\n# variables\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\nexpand_pkgid_and_map() {\n local PKGID=\"$1\"\n local FUNC=\"$2\"\n if [[ \"$PKGID\" == *\"*\" ]]; then\n local prefix=\"${PKGID%\\*}\"\n echo \"Expanding wildcard for PKGID: $PKGID\"\n for receipt in $(pkgutil --pkgs | grep \"^${prefix}\"); do\n echo \"Processing $receipt\"\n \"$FUNC\" \"$receipt\"\n done\n else\n \"$FUNC\" \"$PKGID\"\n fi\n}\n\nforget_pkg() {\n local PKGID=\"$1\"\n expand_pkgid_and_map \"$PKGID\" forget_receipt\n}\n\nforget_receipt() {\n local PKGID=\"$1\"\n sudo pkgutil --forget \"$PKGID\"\n}\n\nremove_launchctl_service() {\n local service=\"$1\"\n local booleans=(\"true\" \"false\")\n local plist_status\n local paths\n local should_sudo\n\n echo \"Removing launchctl service ${service}\"\n\n # A wildcard label can't be used with launchctl or as a plist name, so expand\n # it to the labels of currently loaded services that match the pattern.\n local services=(\"$service\")\n if [[ \"$service\" == *\"*\"* ]]; then\n local regex\n # Escape regex metacharacters, turn '*' into '.*', and anchor the pattern so\n # it matches a full label rather than a substring.\n regex=$(printf '%s' \"$service\" | sed -e 's/[][(){}.^$+?|\\\\]/\\\\&/g' -e 's/\\*/.*/g')\n regex=\"^${regex}$\"\n services=()\n local id\n # Match every loaded job by label regardless of PID; launchctl list reports\n # loaded-but-not-running jobs with a \"-\" in the PID column.\n while read -r _ _ id; do\n [[ \"$id\" =~ $regex ]] && services+=(\"$id\")\n done < <(launchctl list 2>/dev/null | tail -n +2)\n if [[ ${#services[@]} -eq 0 ]]; then\n echo \"No loaded launchctl service matches ${service}\"\n return\n fi\n fi\n\n local service_label\n for service_label in \"${services[@]}\"; do\n for should_sudo in \"${booleans[@]}\"; do\n plist_status=$(launchctl list \"${service_label}\" 2>/dev/null)\n\n if [[ $plist_status == \\{* ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo launchctl remove \"${service_label}\"\n else\n launchctl remove \"${service_label}\"\n fi\n sleep 1\n fi\n\n paths=(\n \"/Library/LaunchAgents/${service_label}.plist\"\n \"/Library/LaunchDaemons/${service_label}.plist\"\n )\n\n # if not using sudo, prepend the home directory to the paths\n if [[ $should_sudo == \"false\" ]]; then\n for i in \"${!paths[@]}\"; do\n paths[i]=\"${HOME}${paths[i]}\"\n done\n fi\n\n for path in \"${paths[@]}\"; do\n if [[ -e \"$path\" ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo rm -f -- \"$path\"\n else\n rm -f -- \"$path\"\n fi\n fi\n done\n done\n done\n}\n\nremove_pkg_files() {\n local PKGID=\"$1\"\n expand_pkgid_and_map \"$PKGID\" remove_receipt_files\n}\n\nremove_receipt_files() {\n local PKGID=\"$1\"\n local PKGINFO VOLUME INSTALL_LOCATION FULL_INSTALL_LOCATION\n\n echo \"pkgutil --pkg-info-plist \\\"$PKGID\\\"\"\n PKGINFO=$(pkgutil --pkg-info-plist \"$PKGID\")\n VOLUME=$(echo \"$PKGINFO\" | awk '/<key>volume<\\/key>/ {getline; gsub(/.*<string>|<\\/string>.*/, \"\"); print}')\n INSTALL_LOCATION=$(echo \"$PKGINFO\" | awk '/<key>install-location<\\/key>/ {getline; gsub(/.*<string>|<\\/string>.*/, \"\"); print}')\n\n if [ -z \"$INSTALL_LOCATION\" ] || [ \"$INSTALL_LOCATION\" = \"/\" ]; then\n FULL_INSTALL_LOCATION=\"$VOLUME\"\n else\n FULL_INSTALL_LOCATION=\"$VOLUME/$INSTALL_LOCATION\"\n FULL_INSTALL_LOCATION=$(echo \"$FULL_INSTALL_LOCATION\" | sed 's|//|/|g')\n fi\n\n echo \"sudo pkgutil --only-files --files \\\"$PKGID\\\" | sed \\\"s|^|${FULL_INSTALL_LOCATION}/|\\\" | tr '\\\\\\\\n' '\\\\\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\"\n sudo pkgutil --only-files --files \"$PKGID\" | sed \"s|^|/${INSTALL_LOCATION}/|\" | tr '\\n' '\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\n\n echo \"sudo pkgutil --only-dirs --files \\\"$PKGID\\\" | sed \\\"s|^|${FULL_INSTALL_LOCATION}/|\\\" | grep '\\\\.app$' | tr '\\\\\\\\n' '\\\\\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\"\n sudo pkgutil --only-dirs --files \"$PKGID\" | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" | grep '\\.app$' | tr '\\n' '\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\n\n root_app_dir=$(\n sudo pkgutil --only-dirs --files \"$PKGID\" \\\n | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" \\\n | grep 'Applications' \\\n | awk '{ print length, $0 }' \\\n | sort -n \\\n | head -n1 \\\n | cut -d' ' -f2-\n )\n if [ -n \"$root_app_dir\" ]; then\n echo \"sudo rmdir -p \\\"$root_app_dir\\\" 2>/dev/null || :\"\n sudo rmdir -p \"$root_app_dir\" 2>/dev/null || :\n fi\n}\n\ntrash() {\n local logged_in_user=\"$1\"\n local target_file=\"$2\"\n local timestamp=\"$(date +%Y-%m-%d-%s)\"\n local rand=\"$(jot -r 1 0 99999)\"\n\n # replace ~ with /Users/$logged_in_user\n if [[ \"$target_file\" == ~* ]]; then\n target_file=\"/Users/$logged_in_user${target_file:1}\"\n fi\n\n local trash=\"/Users/$logged_in_user/.Trash\"\n\n # If the target contains glob characters, expand it and move each match.\n if [[ \"$target_file\" == *[*?[]* ]]; then\n local file file_name\n local matched=false\n local i=0\n # compgen -G expands the (quoted) pattern itself, so paths containing\n # spaces glob correctly; reading line by line keeps each match intact.\n while IFS= read -r file; do\n [[ -n \"$file\" ]] || continue\n [[ -e \"$file\" || -L \"$file\" ]] || continue\n matched=true\n i=$((i + 1))\n file_name=\"$(basename \"$file\")\"\n echo \"removing $file.\"\n # The per-match counter keeps matches that share a basename from\n # overwriting each other in the trash.\n mv -f \"$file\" \"$trash/${file_name}_${timestamp}_${rand}_${i}\"\n done < <(compgen -G \"$target_file\" 2>/dev/null)\n if [[ \"$matched\" == false ]]; then\n echo \"$target_file doesn't exist.\"\n fi\n return\n fi\n\n local file_name=\"$(basename \"${target_file}\")\"\n\n if [[ -e \"$target_file\" ]]; then\n echo \"removing $target_file.\"\n mv -f \"$target_file\" \"$trash/${file_name}_${timestamp}_${rand}\"\n else\n echo \"$target_file doesn't exist.\"\n fi\n}\n\nremove_launchctl_service 'aviatrix.vpn.client.rp.plist'\nremove_pkg_files 'com.Aviatrix.VPNClient'\nforget_pkg 'com.Aviatrix.VPNClient'\nsudo rm -rf '/Applications/Aviatrix VPN Client.app'\ntrash $LOGGED_IN_USER '~/Library/Aviatrix'\ntrash $LOGGED_IN_USER '~/Library/Logs/AviatrixVPNC'\ntrash $LOGGED_IN_USER '~/Library/Preferences/org.pythonmac.unspecified.AviatrixVPNClient.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/org.pythonmac.unspecified.AviatrixVPNClient.savedState'\n"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Avoid generating .plist.plist cleanup paths.

Line 20 passes aviatrix.vpn.client.rp.plist, but remove_launchctl_service appends .plist when building LaunchAgent/LaunchDaemon paths, so the actual plist cleanup targets become aviatrix.vpn.client.rp.plist.plist.

Proposed fix
       paths=(
-        "/Library/LaunchAgents/${service_label}.plist"
-        "/Library/LaunchDaemons/${service_label}.plist"
+        "/Library/LaunchAgents/${service_label%.plist}.plist"
+        "/Library/LaunchDaemons/${service_label%.plist}.plist"
       )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"7ef6154e": "#!/bin/bash\n\n# variables\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\nexpand_pkgid_and_map() {\n local PKGID=\"$1\"\n local FUNC=\"$2\"\n if [[ \"$PKGID\" == *\"*\" ]]; then\n local prefix=\"${PKGID%\\*}\"\n echo \"Expanding wildcard for PKGID: $PKGID\"\n for receipt in $(pkgutil --pkgs | grep \"^${prefix}\"); do\n echo \"Processing $receipt\"\n \"$FUNC\" \"$receipt\"\n done\n else\n \"$FUNC\" \"$PKGID\"\n fi\n}\n\nforget_pkg() {\n local PKGID=\"$1\"\n expand_pkgid_and_map \"$PKGID\" forget_receipt\n}\n\nforget_receipt() {\n local PKGID=\"$1\"\n sudo pkgutil --forget \"$PKGID\"\n}\n\nremove_launchctl_service() {\n local service=\"$1\"\n local booleans=(\"true\" \"false\")\n local plist_status\n local paths\n local should_sudo\n\n echo \"Removing launchctl service ${service}\"\n\n # A wildcard label can't be used with launchctl or as a plist name, so expand\n # it to the labels of currently loaded services that match the pattern.\n local services=(\"$service\")\n if [[ \"$service\" == *\"*\"* ]]; then\n local regex\n # Escape regex metacharacters, turn '*' into '.*', and anchor the pattern so\n # it matches a full label rather than a substring.\n regex=$(printf '%s' \"$service\" | sed -e 's/[][(){}.^$+?|\\\\]/\\\\&/g' -e 's/\\*/.*/g')\n regex=\"^${regex}$\"\n services=()\n local id\n # Match every loaded job by label regardless of PID; launchctl list reports\n # loaded-but-not-running jobs with a \"-\" in the PID column.\n while read -r _ _ id; do\n [[ \"$id\" =~ $regex ]] && services+=(\"$id\")\n done < <(launchctl list 2>/dev/null | tail -n +2)\n if [[ ${#services[@]} -eq 0 ]]; then\n echo \"No loaded launchctl service matches ${service}\"\n return\n fi\n fi\n\n local service_label\n for service_label in \"${services[@]}\"; do\n for should_sudo in \"${booleans[@]}\"; do\n plist_status=$(launchctl list \"${service_label}\" 2>/dev/null)\n\n if [[ $plist_status == \\{* ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo launchctl remove \"${service_label}\"\n else\n launchctl remove \"${service_label}\"\n fi\n sleep 1\n fi\n\n paths=(\n \"/Library/LaunchAgents/${service_label}.plist\"\n \"/Library/LaunchDaemons/${service_label}.plist\"\n )\n\n # if not using sudo, prepend the home directory to the paths\n if [[ $should_sudo == \"false\" ]]; then\n for i in \"${!paths[@]}\"; do\n paths[i]=\"${HOME}${paths[i]}\"\n done\n fi\n\n for path in \"${paths[@]}\"; do\n if [[ -e \"$path\" ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo rm -f -- \"$path\"\n else\n rm -f -- \"$path\"\n fi\n fi\n done\n done\n done\n}\n\nremove_pkg_files() {\n local PKGID=\"$1\"\n expand_pkgid_and_map \"$PKGID\" remove_receipt_files\n}\n\nremove_receipt_files() {\n local PKGID=\"$1\"\n local PKGINFO VOLUME INSTALL_LOCATION FULL_INSTALL_LOCATION\n\n echo \"pkgutil --pkg-info-plist \\\"$PKGID\\\"\"\n PKGINFO=$(pkgutil --pkg-info-plist \"$PKGID\")\n VOLUME=$(echo \"$PKGINFO\" | awk '/<key>volume<\\/key>/ {getline; gsub(/.*<string>|<\\/string>.*/, \"\"); print}')\n INSTALL_LOCATION=$(echo \"$PKGINFO\" | awk '/<key>install-location<\\/key>/ {getline; gsub(/.*<string>|<\\/string>.*/, \"\"); print}')\n\n if [ -z \"$INSTALL_LOCATION\" ] || [ \"$INSTALL_LOCATION\" = \"/\" ]; then\n FULL_INSTALL_LOCATION=\"$VOLUME\"\n else\n FULL_INSTALL_LOCATION=\"$VOLUME/$INSTALL_LOCATION\"\n FULL_INSTALL_LOCATION=$(echo \"$FULL_INSTALL_LOCATION\" | sed 's|//|/|g')\n fi\n\n echo \"sudo pkgutil --only-files --files \\\"$PKGID\\\" | sed \\\"s|^|${FULL_INSTALL_LOCATION}/|\\\" | tr '\\\\\\\\n' '\\\\\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\"\n sudo pkgutil --only-files --files \"$PKGID\" | sed \"s|^|/${INSTALL_LOCATION}/|\" | tr '\\n' '\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\n\n echo \"sudo pkgutil --only-dirs --files \\\"$PKGID\\\" | sed \\\"s|^|${FULL_INSTALL_LOCATION}/|\\\" | grep '\\\\.app$' | tr '\\\\\\\\n' '\\\\\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\"\n sudo pkgutil --only-dirs --files \"$PKGID\" | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" | grep '\\.app$' | tr '\\n' '\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\n\n root_app_dir=$(\n sudo pkgutil --only-dirs --files \"$PKGID\" \\\n | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" \\\n | grep 'Applications' \\\n | awk '{ print length, $0 }' \\\n | sort -n \\\n | head -n1 \\\n | cut -d' ' -f2-\n )\n if [ -n \"$root_app_dir\" ]; then\n echo \"sudo rmdir -p \\\"$root_app_dir\\\" 2>/dev/null || :\"\n sudo rmdir -p \"$root_app_dir\" 2>/dev/null || :\n fi\n}\n\ntrash() {\n local logged_in_user=\"$1\"\n local target_file=\"$2\"\n local timestamp=\"$(date +%Y-%m-%d-%s)\"\n local rand=\"$(jot -r 1 0 99999)\"\n\n # replace ~ with /Users/$logged_in_user\n if [[ \"$target_file\" == ~* ]]; then\n target_file=\"/Users/$logged_in_user${target_file:1}\"\n fi\n\n local trash=\"/Users/$logged_in_user/.Trash\"\n\n # If the target contains glob characters, expand it and move each match.\n if [[ \"$target_file\" == *[*?[]* ]]; then\n local file file_name\n local matched=false\n local i=0\n # compgen -G expands the (quoted) pattern itself, so paths containing\n # spaces glob correctly; reading line by line keeps each match intact.\n while IFS= read -r file; do\n [[ -n \"$file\" ]] || continue\n [[ -e \"$file\" || -L \"$file\" ]] || continue\n matched=true\n i=$((i + 1))\n file_name=\"$(basename \"$file\")\"\n echo \"removing $file.\"\n # The per-match counter keeps matches that share a basename from\n # overwriting each other in the trash.\n mv -f \"$file\" \"$trash/${file_name}_${timestamp}_${rand}_${i}\"\n done < <(compgen -G \"$target_file\" 2>/dev/null)\n if [[ \"$matched\" == false ]]; then\n echo \"$target_file doesn't exist.\"\n fi\n return\n fi\n\n local file_name=\"$(basename \"${target_file}\")\"\n\n if [[ -e \"$target_file\" ]]; then\n echo \"removing $target_file.\"\n mv -f \"$target_file\" \"$trash/${file_name}_${timestamp}_${rand}\"\n else\n echo \"$target_file doesn't exist.\"\n fi\n}\n\nremove_launchctl_service 'aviatrix.vpn.client.rp.plist'\nremove_pkg_files 'com.Aviatrix.VPNClient'\nforget_pkg 'com.Aviatrix.VPNClient'\nsudo rm -rf '/Applications/Aviatrix VPN Client.app'\ntrash $LOGGED_IN_USER '~/Library/Aviatrix'\ntrash $LOGGED_IN_USER '~/Library/Logs/AviatrixVPNC'\ntrash $LOGGED_IN_USER '~/Library/Preferences/org.pythonmac.unspecified.AviatrixVPNClient.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/org.pythonmac.unspecified.AviatrixVPNClient.savedState'\n"
remove_launchctl_service() {
local service="$1"
local booleans=("true" "false")
local plist_status
local paths
local should_sudo
echo "Removing launchctl service ${service}"
# A wildcard label can't be used with launchctl or as a plist name, so expand
# it to the labels of currently loaded services that match the pattern.
local services=("$service")
if [[ "$service" == *"*"* ]]; then
local regex
# Escape regex metacharacters, turn '*' into '.*', and anchor the pattern so
# it matches a full label rather than a substring.
regex=$(printf '%s' "$service" | sed -e 's/[][(){}.^$+?|\\]/\\&/g' -e 's/\*/.*/g')
regex="^${regex}$"
services=()
local id
# Match every loaded job by label regardless of PID; launchctl list reports
# loaded-but-not-running jobs with a "-" in the PID column.
while read -r _ _ id; do
[[ "$id" =~ $regex ]] && services+=("$id")
done < <(launchctl list 2>/dev/null | tail -n +2)
if [[ ${`#services`[@]} -eq 0 ]]; then
echo "No loaded launchctl service matches ${service}"
return
fi
fi
local service_label
for service_label in "${services[@]}"; do
for should_sudo in "${booleans[@]}"; do
plist_status=$(launchctl list "${service_label}" 2>/dev/null)
if [[ $plist_status == \{* ]]; then
if [[ $should_sudo == "true" ]]; then
sudo launchctl remove "${service_label}"
else
launchctl remove "${service_label}"
fi
sleep 1
fi
paths=(
"/Library/LaunchAgents/${service_label%.plist}.plist"
"/Library/LaunchDaemons/${service_label%.plist}.plist"
)
# if not using sudo, prepend the home directory to the paths
if [[ $should_sudo == "false" ]]; then
for i in "${!paths[@]}"; do
paths[i]="${HOME}${paths[i]}"
done
fi
for path in "${paths[@]}"; do
if [[ -e "$path" ]]; then
if [[ $should_sudo == "true" ]]; then
sudo rm -f -- "$path"
else
rm -f -- "$path"
fi
fi
done
done
done
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ee/maintained-apps/outputs/aviatrix-vpn-client/darwin.json` at line 20, The
launchctl cleanup input already includes the .plist suffix, but
remove_launchctl_service appends .plist again when building the
LaunchAgents/LaunchDaemons paths, causing .plist.plist targets. Update the call
site or the path construction in remove_launchctl_service so service labels are
handled consistently, and use the existing symbols remove_launchctl_service and
the aviatrix.vpn.client.rp.plist invocation to locate the fix.

"refs": {
"6e4fcb5d": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nTMPDIR=$(dirname \"$(realpath \"$INSTALLER_PATH\")\")\n# functions\n\nquit_and_track_application() {\n local bundle_id=\"$1\"\n local var_name=\"APP_WAS_RUNNING_$(echo \"$bundle_id\" | tr '.-' '__')\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e \"application id \\\"$bundle_id\\\" is running\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n eval \"export $var_name=0\"\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n eval \"export $var_name=0\"\n return\n fi\n\n # App was running, mark it for relaunch\n eval \"export $var_name=1\"\n echo \"Application '$bundle_id' was running; will relaunch after installation.\"\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" >/dev/null 2>&1; then\n if ! pgrep -f \"$bundle_id\" >/dev/null 2>&1; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n fi\n sleep 1\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n fi\n}\n\n\nrelaunch_application() {\n local bundle_id=\"$1\"\n local var_name=\"APP_WAS_RUNNING_$(echo \"$bundle_id\" | tr '.-' '__')\"\n local was_running\n\n # Check if the app was running before installation\n eval \"was_running=\\$$var_name\"\n if [[ \"$was_running\" != \"1\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping relaunching application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Relaunching application '$bundle_id'...\"\n\n # Launch the app in the logged-in user's GUI session. Apps launched by root\n # won't register with the user's Dock/GUI, so run 'open' as the console user.\n # Use 'launchctl asuser' to bootstrap into the console user's Mach namespace\n # and GUI session — 'sudo -u' alone doesn't do this, which can cause\n # LSOpenURLsWithRole() failures even when 'open' exits 0.\n local open_status=0\n if [[ $EUID -eq 0 ]]; then\n local console_uid\n console_uid=$(id -u \"$console_user\")\n /bin/launchctl asuser \"$console_uid\" sudo -u \"$console_user\" open -b \"$bundle_id\" >/dev/null 2>&1 || open_status=$?\n else\n open -b \"$bundle_id\" >/dev/null 2>&1 || open_status=$?\n fi\n\n if [[ $open_status -eq 0 ]]; then\n echo \"Application '$bundle_id' relaunched successfully.\"\n else\n echo \"Failed to relaunch application '$bundle_id'.\"\n fi\n}\n\n\n# install pkg files\nquit_and_track_application 'com.box.desktop'\nsudo installer -pkg \"$TMPDIR/BoxDrive-2.52.312.pkg\" -target /\nrelaunch_application 'com.box.desktop'\n",
"7b684425": "#!/bin/bash\n\n# variables\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\nexpand_pkgid_and_map() {\n local PKGID=\"$1\"\n local FUNC=\"$2\"\n if [[ \"$PKGID\" == *\"*\" ]]; then\n local prefix=\"${PKGID%\\*}\"\n echo \"Expanding wildcard for PKGID: $PKGID\"\n for receipt in $(pkgutil --pkgs | grep \"^${prefix}\"); do\n echo \"Processing $receipt\"\n \"$FUNC\" \"$receipt\"\n done\n else\n \"$FUNC\" \"$PKGID\"\n fi\n}\n\nforget_pkg() {\n local PKGID=\"$1\"\n expand_pkgid_and_map \"$PKGID\" forget_receipt\n}\n\nforget_receipt() {\n local PKGID=\"$1\"\n sudo pkgutil --forget \"$PKGID\"\n}\n\nquit_application() {\n local bundle_id=\"$1\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e \"application id \\\"$bundle_id\\\" is running\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" >/dev/null 2>&1; then\n if ! pgrep -f \"$bundle_id\" >/dev/null 2>&1; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n fi\n sleep 1\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n fi\n}\n\n\nremove_launchctl_service() {\n local service=\"$1\"\n local booleans=(\"true\" \"false\")\n local plist_status\n local paths\n local should_sudo\n\n echo \"Removing launchctl service ${service}\"\n\n for should_sudo in \"${booleans[@]}\"; do\n plist_status=$(launchctl list \"${service}\" 2>/dev/null)\n\n if [[ $plist_status == \\{* ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo launchctl remove \"${service}\"\n else\n launchctl remove \"${service}\"\n fi\n sleep 1\n fi\n\n paths=(\n \"/Library/LaunchAgents/${service}.plist\"\n \"/Library/LaunchDaemons/${service}.plist\"\n )\n\n # if not using sudo, prepend the home directory to the paths\n if [[ $should_sudo == \"false\" ]]; then\n for i in \"${!paths[@]}\"; do\n paths[i]=\"${HOME}${paths[i]}\"\n done\n fi\n\n for path in \"${paths[@]}\"; do\n if [[ -e \"$path\" ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo rm -f -- \"$path\"\n else\n rm -f -- \"$path\"\n fi\n fi\n done\n done\n}\n\nremove_pkg_files() {\n local PKGID=\"$1\"\n expand_pkgid_and_map \"$PKGID\" remove_receipt_files\n}\n\nremove_receipt_files() {\n local PKGID=\"$1\"\n local PKGINFO VOLUME INSTALL_LOCATION FULL_INSTALL_LOCATION\n\n echo \"pkgutil --pkg-info-plist \\\"$PKGID\\\"\"\n PKGINFO=$(pkgutil --pkg-info-plist \"$PKGID\")\n VOLUME=$(echo \"$PKGINFO\" | awk '/<key>volume<\\/key>/ {getline; gsub(/.*<string>|<\\/string>.*/, \"\"); print}')\n INSTALL_LOCATION=$(echo \"$PKGINFO\" | awk '/<key>install-location<\\/key>/ {getline; gsub(/.*<string>|<\\/string>.*/, \"\"); print}')\n\n if [ -z \"$INSTALL_LOCATION\" ] || [ \"$INSTALL_LOCATION\" = \"/\" ]; then\n FULL_INSTALL_LOCATION=\"$VOLUME\"\n else\n FULL_INSTALL_LOCATION=\"$VOLUME/$INSTALL_LOCATION\"\n FULL_INSTALL_LOCATION=$(echo \"$FULL_INSTALL_LOCATION\" | sed 's|//|/|g')\n fi\n\n echo \"sudo pkgutil --only-files --files \\\"$PKGID\\\" | sed \\\"s|^|${FULL_INSTALL_LOCATION}/|\\\" | tr '\\\\\\\\n' '\\\\\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\"\n sudo pkgutil --only-files --files \"$PKGID\" | sed \"s|^|/${INSTALL_LOCATION}/|\" | tr '\\n' '\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\n\n echo \"sudo pkgutil --only-dirs --files \\\"$PKGID\\\" | sed \\\"s|^|${FULL_INSTALL_LOCATION}/|\\\" | grep '\\\\.app$' | tr '\\\\\\\\n' '\\\\\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\"\n sudo pkgutil --only-dirs --files \"$PKGID\" | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" | grep '\\.app$' | tr '\\n' '\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\n\n root_app_dir=$(\n sudo pkgutil --only-dirs --files \"$PKGID\" \\\n | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" \\\n | grep 'Applications' \\\n | awk '{ print length, $0 }' \\\n | sort -n \\\n | head -n1 \\\n | cut -d' ' -f2-\n )\n if [ -n \"$root_app_dir\" ]; then\n echo \"sudo rmdir -p \\\"$root_app_dir\\\" 2>/dev/null || :\"\n sudo rmdir -p \"$root_app_dir\" 2>/dev/null || :\n fi\n}\n\ntrash() {\n local logged_in_user=\"$1\"\n local target_file=\"$2\"\n local timestamp=\"$(date +%Y-%m-%d-%s)\"\n local rand=\"$(jot -r 1 0 99999)\"\n\n # replace ~ with /Users/$logged_in_user\n if [[ \"$target_file\" == ~* ]]; then\n target_file=\"/Users/$logged_in_user${target_file:1}\"\n fi\n\n local trash=\"/Users/$logged_in_user/.Trash\"\n\n # If the target contains glob characters, expand it and move each match.\n if [[ \"$target_file\" == *[*?[]* ]]; then\n local file file_name\n local matched=false\n local i=0\n # compgen -G expands the (quoted) pattern itself, so paths containing\n # spaces glob correctly; reading line by line keeps each match intact.\n while IFS= read -r file; do\n [[ -n \"$file\" ]] || continue\n [[ -e \"$file\" || -L \"$file\" ]] || continue\n matched=true\n i=$((i + 1))\n file_name=\"$(basename \"$file\")\"\n echo \"removing $file.\"\n # The per-match counter keeps matches that share a basename from\n # overwriting each other in the trash.\n mv -f \"$file\" \"$trash/${file_name}_${timestamp}_${rand}_${i}\"\n done < <(compgen -G \"$target_file\" 2>/dev/null)\n if [[ \"$matched\" == false ]]; then\n echo \"$target_file doesn't exist.\"\n fi\n return\n fi\n\n local file_name=\"$(basename \"${target_file}\")\"\n\n if [[ -e \"$target_file\" ]]; then\n echo \"removing $target_file.\"\n mv -f \"$target_file\" \"$trash/${file_name}_${timestamp}_${rand}\"\n else\n echo \"$target_file doesn't exist.\"\n fi\n}\n\n(cd /Users/$LOGGED_IN_USER; sudo -u $LOGGED_IN_USER fileproviderctl domain remove -A com.box.desktop.boxfileprovider)\n(cd /Users/$LOGGED_IN_USER; sudo -u $LOGGED_IN_USER /Applications/Box.app/Contents/MacOS/fpe/streem --remove-fpe-domain-and-archive-unsynced-content Box)\n(cd /Users/$LOGGED_IN_USER; sudo -u $LOGGED_IN_USER /Applications/Box.app/Contents/MacOS/fpe/streem --remove-fpe-domain-and-preserve-unsynced-content Box)\n(cd /Users/$LOGGED_IN_USER; defaults delete com.box.desktop)\necho \"${LOGGED_IN_USER} ALL = (root) NOPASSWD: /Library/Application\\ Support/Box/uninstall_box_drive_r\" >> /etc/sudoers.d/box_uninstall\nremove_launchctl_service 'com.box.desktop.helper'\nquit_application 'com.box.Box-Local-Com-Server'\nquit_application 'com.box.desktop'\nquit_application 'com.box.desktop.findersyncext'\nquit_application 'com.box.desktop.helper'\nquit_application 'com.box.desktop.ui'\n(cd /Users/$LOGGED_IN_USER && sudo -u \"$LOGGED_IN_USER\" '/Library/Application Support/Box/uninstall_box_drive')\nremove_pkg_files 'com.box.desktop.installer.*'\nforget_pkg 'com.box.desktop.installer.*'\nrm /etc/sudoers.d/box_uninstall\ntrash $LOGGED_IN_USER '~/.Box_*'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Box/Box'\ntrash $LOGGED_IN_USER '~/Library/Application Support/FileProvider/com.box.desktop.boxfileprovider'\ntrash $LOGGED_IN_USER '~/Library/Containers/com.box.desktop.findersyncext'\ntrash $LOGGED_IN_USER '~/Library/Logs/Box/Box'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.box.desktop.plist'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.box.desktop.ui.plist'\n"
"0b49ca2e": "#!/bin/bash\n\n# variables\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\nexpand_pkgid_and_map() {\n local PKGID=\"$1\"\n local FUNC=\"$2\"\n if [[ \"$PKGID\" == *\"*\" ]]; then\n local prefix=\"${PKGID%\\*}\"\n echo \"Expanding wildcard for PKGID: $PKGID\"\n for receipt in $(pkgutil --pkgs | grep \"^${prefix}\"); do\n echo \"Processing $receipt\"\n \"$FUNC\" \"$receipt\"\n done\n else\n \"$FUNC\" \"$PKGID\"\n fi\n}\n\nforget_pkg() {\n local PKGID=\"$1\"\n expand_pkgid_and_map \"$PKGID\" forget_receipt\n}\n\nforget_receipt() {\n local PKGID=\"$1\"\n sudo pkgutil --forget \"$PKGID\"\n}\n\nquit_application() {\n local bundle_id=\"$1\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e \"application id \\\"$bundle_id\\\" is running\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" >/dev/null 2>&1; then\n if ! pgrep -f \"$bundle_id\" >/dev/null 2>&1; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n fi\n sleep 1\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n fi\n}\n\n\nremove_launchctl_service() {\n local service=\"$1\"\n local booleans=(\"true\" \"false\")\n local plist_status\n local paths\n local should_sudo\n\n echo \"Removing launchctl service ${service}\"\n\n # A wildcard label can't be used with launchctl or as a plist name, so expand\n # it to the labels of currently loaded services that match the pattern.\n local services=(\"$service\")\n if [[ \"$service\" == *\"*\"* ]]; then\n local regex\n # Escape regex metacharacters, turn '*' into '.*', and anchor the pattern so\n # it matches a full label rather than a substring.\n regex=$(printf '%s' \"$service\" | sed -e 's/[][(){}.^$+?|\\\\]/\\\\&/g' -e 's/\\*/.*/g')\n regex=\"^${regex}$\"\n services=()\n local id\n # Match every loaded job by label regardless of PID; launchctl list reports\n # loaded-but-not-running jobs with a \"-\" in the PID column.\n while read -r _ _ id; do\n [[ \"$id\" =~ $regex ]] && services+=(\"$id\")\n done < <(launchctl list 2>/dev/null | tail -n +2)\n if [[ ${#services[@]} -eq 0 ]]; then\n echo \"No loaded launchctl service matches ${service}\"\n return\n fi\n fi\n\n local service_label\n for service_label in \"${services[@]}\"; do\n for should_sudo in \"${booleans[@]}\"; do\n plist_status=$(launchctl list \"${service_label}\" 2>/dev/null)\n\n if [[ $plist_status == \\{* ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo launchctl remove \"${service_label}\"\n else\n launchctl remove \"${service_label}\"\n fi\n sleep 1\n fi\n\n paths=(\n \"/Library/LaunchAgents/${service_label}.plist\"\n \"/Library/LaunchDaemons/${service_label}.plist\"\n )\n\n # if not using sudo, prepend the home directory to the paths\n if [[ $should_sudo == \"false\" ]]; then\n for i in \"${!paths[@]}\"; do\n paths[i]=\"${HOME}${paths[i]}\"\n done\n fi\n\n for path in \"${paths[@]}\"; do\n if [[ -e \"$path\" ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo rm -f -- \"$path\"\n else\n rm -f -- \"$path\"\n fi\n fi\n done\n done\n done\n}\n\nremove_pkg_files() {\n local PKGID=\"$1\"\n expand_pkgid_and_map \"$PKGID\" remove_receipt_files\n}\n\nremove_receipt_files() {\n local PKGID=\"$1\"\n local PKGINFO VOLUME INSTALL_LOCATION FULL_INSTALL_LOCATION\n\n echo \"pkgutil --pkg-info-plist \\\"$PKGID\\\"\"\n PKGINFO=$(pkgutil --pkg-info-plist \"$PKGID\")\n VOLUME=$(echo \"$PKGINFO\" | awk '/<key>volume<\\/key>/ {getline; gsub(/.*<string>|<\\/string>.*/, \"\"); print}')\n INSTALL_LOCATION=$(echo \"$PKGINFO\" | awk '/<key>install-location<\\/key>/ {getline; gsub(/.*<string>|<\\/string>.*/, \"\"); print}')\n\n if [ -z \"$INSTALL_LOCATION\" ] || [ \"$INSTALL_LOCATION\" = \"/\" ]; then\n FULL_INSTALL_LOCATION=\"$VOLUME\"\n else\n FULL_INSTALL_LOCATION=\"$VOLUME/$INSTALL_LOCATION\"\n FULL_INSTALL_LOCATION=$(echo \"$FULL_INSTALL_LOCATION\" | sed 's|//|/|g')\n fi\n\n echo \"sudo pkgutil --only-files --files \\\"$PKGID\\\" | sed \\\"s|^|${FULL_INSTALL_LOCATION}/|\\\" | tr '\\\\\\\\n' '\\\\\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\"\n sudo pkgutil --only-files --files \"$PKGID\" | sed \"s|^|/${INSTALL_LOCATION}/|\" | tr '\\n' '\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\n\n echo \"sudo pkgutil --only-dirs --files \\\"$PKGID\\\" | sed \\\"s|^|${FULL_INSTALL_LOCATION}/|\\\" | grep '\\\\.app$' | tr '\\\\\\\\n' '\\\\\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\"\n sudo pkgutil --only-dirs --files \"$PKGID\" | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" | grep '\\.app$' | tr '\\n' '\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\n\n root_app_dir=$(\n sudo pkgutil --only-dirs --files \"$PKGID\" \\\n | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" \\\n | grep 'Applications' \\\n | awk '{ print length, $0 }' \\\n | sort -n \\\n | head -n1 \\\n | cut -d' ' -f2-\n )\n if [ -n \"$root_app_dir\" ]; then\n echo \"sudo rmdir -p \\\"$root_app_dir\\\" 2>/dev/null || :\"\n sudo rmdir -p \"$root_app_dir\" 2>/dev/null || :\n fi\n}\n\ntrash() {\n local logged_in_user=\"$1\"\n local target_file=\"$2\"\n local timestamp=\"$(date +%Y-%m-%d-%s)\"\n local rand=\"$(jot -r 1 0 99999)\"\n\n # replace ~ with /Users/$logged_in_user\n if [[ \"$target_file\" == ~* ]]; then\n target_file=\"/Users/$logged_in_user${target_file:1}\"\n fi\n\n local trash=\"/Users/$logged_in_user/.Trash\"\n\n # If the target contains glob characters, expand it and move each match.\n if [[ \"$target_file\" == *[*?[]* ]]; then\n local file file_name\n local matched=false\n local i=0\n # compgen -G expands the (quoted) pattern itself, so paths containing\n # spaces glob correctly; reading line by line keeps each match intact.\n while IFS= read -r file; do\n [[ -n \"$file\" ]] || continue\n [[ -e \"$file\" || -L \"$file\" ]] || continue\n matched=true\n i=$((i + 1))\n file_name=\"$(basename \"$file\")\"\n echo \"removing $file.\"\n # The per-match counter keeps matches that share a basename from\n # overwriting each other in the trash.\n mv -f \"$file\" \"$trash/${file_name}_${timestamp}_${rand}_${i}\"\n done < <(compgen -G \"$target_file\" 2>/dev/null)\n if [[ \"$matched\" == false ]]; then\n echo \"$target_file doesn't exist.\"\n fi\n return\n fi\n\n local file_name=\"$(basename \"${target_file}\")\"\n\n if [[ -e \"$target_file\" ]]; then\n echo \"removing $target_file.\"\n mv -f \"$target_file\" \"$trash/${file_name}_${timestamp}_${rand}\"\n else\n echo \"$target_file doesn't exist.\"\n fi\n}\n\n(cd /Users/$LOGGED_IN_USER; sudo -u $LOGGED_IN_USER fileproviderctl domain remove -A com.box.desktop.boxfileprovider)\n(cd /Users/$LOGGED_IN_USER; sudo -u $LOGGED_IN_USER /Applications/Box.app/Contents/MacOS/fpe/streem --remove-fpe-domain-and-archive-unsynced-content Box)\n(cd /Users/$LOGGED_IN_USER; sudo -u $LOGGED_IN_USER /Applications/Box.app/Contents/MacOS/fpe/streem --remove-fpe-domain-and-preserve-unsynced-content Box)\n(cd /Users/$LOGGED_IN_USER; defaults delete com.box.desktop)\necho \"${LOGGED_IN_USER} ALL = (root) NOPASSWD: /Library/Application\\ Support/Box/uninstall_box_drive_r\" >> /etc/sudoers.d/box_uninstall\nremove_launchctl_service 'com.box.desktop.helper'\nquit_application 'com.box.Box-Local-Com-Server'\nquit_application 'com.box.desktop'\nquit_application 'com.box.desktop.findersyncext'\nquit_application 'com.box.desktop.helper'\nquit_application 'com.box.desktop.ui'\n(cd /Users/$LOGGED_IN_USER && sudo -u \"$LOGGED_IN_USER\" '/Library/Application Support/Box/uninstall_box_drive')\nremove_pkg_files 'com.box.desktop.installer.*'\nforget_pkg 'com.box.desktop.installer.*'\nrm /etc/sudoers.d/box_uninstall\ntrash $LOGGED_IN_USER '~/.Box_*'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Box/Box'\ntrash $LOGGED_IN_USER '~/Library/Application Support/FileProvider/com.box.desktop.boxfileprovider'\ntrash $LOGGED_IN_USER '~/Library/Containers/com.box.desktop.findersyncext'\ntrash $LOGGED_IN_USER '~/Library/Logs/Box/Box'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.box.desktop.plist'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.box.desktop.ui.plist'\n",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Match wildcard package prefixes literally.

Line 19 expands com.box.desktop.installer.*, but grep "^${prefix}" treats dots in com.box.desktop.installer. as regex wildcards, so unrelated receipts can be selected and removed.

Proposed fix
-    for receipt in $(pkgutil --pkgs | grep "^${prefix}"); do
+    while IFS= read -r receipt; do
       echo "Processing $receipt"
       "$FUNC" "$receipt"
-    done
+    done < <(pkgutil --pkgs | awk -v prefix="$prefix" 'index($0, prefix) == 1')
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"0b49ca2e": "#!/bin/bash\n\n# variables\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\nexpand_pkgid_and_map() {\n local PKGID=\"$1\"\n local FUNC=\"$2\"\n if [[ \"$PKGID\" == *\"*\" ]]; then\n local prefix=\"${PKGID%\\*}\"\n echo \"Expanding wildcard for PKGID: $PKGID\"\n for receipt in $(pkgutil --pkgs | grep \"^${prefix}\"); do\n echo \"Processing $receipt\"\n \"$FUNC\" \"$receipt\"\n done\n else\n \"$FUNC\" \"$PKGID\"\n fi\n}\n\nforget_pkg() {\n local PKGID=\"$1\"\n expand_pkgid_and_map \"$PKGID\" forget_receipt\n}\n\nforget_receipt() {\n local PKGID=\"$1\"\n sudo pkgutil --forget \"$PKGID\"\n}\n\nquit_application() {\n local bundle_id=\"$1\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e \"application id \\\"$bundle_id\\\" is running\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" >/dev/null 2>&1; then\n if ! pgrep -f \"$bundle_id\" >/dev/null 2>&1; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n fi\n sleep 1\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n fi\n}\n\n\nremove_launchctl_service() {\n local service=\"$1\"\n local booleans=(\"true\" \"false\")\n local plist_status\n local paths\n local should_sudo\n\n echo \"Removing launchctl service ${service}\"\n\n # A wildcard label can't be used with launchctl or as a plist name, so expand\n # it to the labels of currently loaded services that match the pattern.\n local services=(\"$service\")\n if [[ \"$service\" == *\"*\"* ]]; then\n local regex\n # Escape regex metacharacters, turn '*' into '.*', and anchor the pattern so\n # it matches a full label rather than a substring.\n regex=$(printf '%s' \"$service\" | sed -e 's/[][(){}.^$+?|\\\\]/\\\\&/g' -e 's/\\*/.*/g')\n regex=\"^${regex}$\"\n services=()\n local id\n # Match every loaded job by label regardless of PID; launchctl list reports\n # loaded-but-not-running jobs with a \"-\" in the PID column.\n while read -r _ _ id; do\n [[ \"$id\" =~ $regex ]] && services+=(\"$id\")\n done < <(launchctl list 2>/dev/null | tail -n +2)\n if [[ ${#services[@]} -eq 0 ]]; then\n echo \"No loaded launchctl service matches ${service}\"\n return\n fi\n fi\n\n local service_label\n for service_label in \"${services[@]}\"; do\n for should_sudo in \"${booleans[@]}\"; do\n plist_status=$(launchctl list \"${service_label}\" 2>/dev/null)\n\n if [[ $plist_status == \\{* ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo launchctl remove \"${service_label}\"\n else\n launchctl remove \"${service_label}\"\n fi\n sleep 1\n fi\n\n paths=(\n \"/Library/LaunchAgents/${service_label}.plist\"\n \"/Library/LaunchDaemons/${service_label}.plist\"\n )\n\n # if not using sudo, prepend the home directory to the paths\n if [[ $should_sudo == \"false\" ]]; then\n for i in \"${!paths[@]}\"; do\n paths[i]=\"${HOME}${paths[i]}\"\n done\n fi\n\n for path in \"${paths[@]}\"; do\n if [[ -e \"$path\" ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo rm -f -- \"$path\"\n else\n rm -f -- \"$path\"\n fi\n fi\n done\n done\n done\n}\n\nremove_pkg_files() {\n local PKGID=\"$1\"\n expand_pkgid_and_map \"$PKGID\" remove_receipt_files\n}\n\nremove_receipt_files() {\n local PKGID=\"$1\"\n local PKGINFO VOLUME INSTALL_LOCATION FULL_INSTALL_LOCATION\n\n echo \"pkgutil --pkg-info-plist \\\"$PKGID\\\"\"\n PKGINFO=$(pkgutil --pkg-info-plist \"$PKGID\")\n VOLUME=$(echo \"$PKGINFO\" | awk '/<key>volume<\\/key>/ {getline; gsub(/.*<string>|<\\/string>.*/, \"\"); print}')\n INSTALL_LOCATION=$(echo \"$PKGINFO\" | awk '/<key>install-location<\\/key>/ {getline; gsub(/.*<string>|<\\/string>.*/, \"\"); print}')\n\n if [ -z \"$INSTALL_LOCATION\" ] || [ \"$INSTALL_LOCATION\" = \"/\" ]; then\n FULL_INSTALL_LOCATION=\"$VOLUME\"\n else\n FULL_INSTALL_LOCATION=\"$VOLUME/$INSTALL_LOCATION\"\n FULL_INSTALL_LOCATION=$(echo \"$FULL_INSTALL_LOCATION\" | sed 's|//|/|g')\n fi\n\n echo \"sudo pkgutil --only-files --files \\\"$PKGID\\\" | sed \\\"s|^|${FULL_INSTALL_LOCATION}/|\\\" | tr '\\\\\\\\n' '\\\\\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\"\n sudo pkgutil --only-files --files \"$PKGID\" | sed \"s|^|/${INSTALL_LOCATION}/|\" | tr '\\n' '\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\n\n echo \"sudo pkgutil --only-dirs --files \\\"$PKGID\\\" | sed \\\"s|^|${FULL_INSTALL_LOCATION}/|\\\" | grep '\\\\.app$' | tr '\\\\\\\\n' '\\\\\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\"\n sudo pkgutil --only-dirs --files \"$PKGID\" | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" | grep '\\.app$' | tr '\\n' '\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\n\n root_app_dir=$(\n sudo pkgutil --only-dirs --files \"$PKGID\" \\\n | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" \\\n | grep 'Applications' \\\n | awk '{ print length, $0 }' \\\n | sort -n \\\n | head -n1 \\\n | cut -d' ' -f2-\n )\n if [ -n \"$root_app_dir\" ]; then\n echo \"sudo rmdir -p \\\"$root_app_dir\\\" 2>/dev/null || :\"\n sudo rmdir -p \"$root_app_dir\" 2>/dev/null || :\n fi\n}\n\ntrash() {\n local logged_in_user=\"$1\"\n local target_file=\"$2\"\n local timestamp=\"$(date +%Y-%m-%d-%s)\"\n local rand=\"$(jot -r 1 0 99999)\"\n\n # replace ~ with /Users/$logged_in_user\n if [[ \"$target_file\" == ~* ]]; then\n target_file=\"/Users/$logged_in_user${target_file:1}\"\n fi\n\n local trash=\"/Users/$logged_in_user/.Trash\"\n\n # If the target contains glob characters, expand it and move each match.\n if [[ \"$target_file\" == *[*?[]* ]]; then\n local file file_name\n local matched=false\n local i=0\n # compgen -G expands the (quoted) pattern itself, so paths containing\n # spaces glob correctly; reading line by line keeps each match intact.\n while IFS= read -r file; do\n [[ -n \"$file\" ]] || continue\n [[ -e \"$file\" || -L \"$file\" ]] || continue\n matched=true\n i=$((i + 1))\n file_name=\"$(basename \"$file\")\"\n echo \"removing $file.\"\n # The per-match counter keeps matches that share a basename from\n # overwriting each other in the trash.\n mv -f \"$file\" \"$trash/${file_name}_${timestamp}_${rand}_${i}\"\n done < <(compgen -G \"$target_file\" 2>/dev/null)\n if [[ \"$matched\" == false ]]; then\n echo \"$target_file doesn't exist.\"\n fi\n return\n fi\n\n local file_name=\"$(basename \"${target_file}\")\"\n\n if [[ -e \"$target_file\" ]]; then\n echo \"removing $target_file.\"\n mv -f \"$target_file\" \"$trash/${file_name}_${timestamp}_${rand}\"\n else\n echo \"$target_file doesn't exist.\"\n fi\n}\n\n(cd /Users/$LOGGED_IN_USER; sudo -u $LOGGED_IN_USER fileproviderctl domain remove -A com.box.desktop.boxfileprovider)\n(cd /Users/$LOGGED_IN_USER; sudo -u $LOGGED_IN_USER /Applications/Box.app/Contents/MacOS/fpe/streem --remove-fpe-domain-and-archive-unsynced-content Box)\n(cd /Users/$LOGGED_IN_USER; sudo -u $LOGGED_IN_USER /Applications/Box.app/Contents/MacOS/fpe/streem --remove-fpe-domain-and-preserve-unsynced-content Box)\n(cd /Users/$LOGGED_IN_USER; defaults delete com.box.desktop)\necho \"${LOGGED_IN_USER} ALL = (root) NOPASSWD: /Library/Application\\ Support/Box/uninstall_box_drive_r\" >> /etc/sudoers.d/box_uninstall\nremove_launchctl_service 'com.box.desktop.helper'\nquit_application 'com.box.Box-Local-Com-Server'\nquit_application 'com.box.desktop'\nquit_application 'com.box.desktop.findersyncext'\nquit_application 'com.box.desktop.helper'\nquit_application 'com.box.desktop.ui'\n(cd /Users/$LOGGED_IN_USER && sudo -u \"$LOGGED_IN_USER\" '/Library/Application Support/Box/uninstall_box_drive')\nremove_pkg_files 'com.box.desktop.installer.*'\nforget_pkg 'com.box.desktop.installer.*'\nrm /etc/sudoers.d/box_uninstall\ntrash $LOGGED_IN_USER '~/.Box_*'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Box/Box'\ntrash $LOGGED_IN_USER '~/Library/Application Support/FileProvider/com.box.desktop.boxfileprovider'\ntrash $LOGGED_IN_USER '~/Library/Containers/com.box.desktop.findersyncext'\ntrash $LOGGED_IN_USER '~/Library/Logs/Box/Box'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.box.desktop.plist'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.box.desktop.ui.plist'\n",
expand_pkgid_and_map() {
local PKGID="$1"
local FUNC="$2"
if [[ "$PKGID" == *"*" ]]; then
local prefix="${PKGID%\*}"
echo "Expanding wildcard for PKGID: $PKGID"
while IFS= read -r receipt; do
echo "Processing $receipt"
"$FUNC" "$receipt"
done < <(pkgutil --pkgs | awk -v prefix="$prefix" 'index($0, prefix) == 1')
else
"$FUNC" "$PKGID"
fi
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ee/maintained-apps/outputs/box-drive/darwin.json` at line 19, The wildcard
package expansion in expand_pkgid_and_map is matching package prefixes as a
regex instead of literally, so dotted PKGID prefixes can overmatch unrelated
receipts. Update the receipt selection logic used by
expand_pkgid_and_map/forget_pkg/remove_pkg_files so the prefix is treated as a
literal string and only the intended com.box.desktop.installer.* receipts are
processed. Keep wildcard behavior for the trailing asterisk, but escape the
non-wildcard characters before comparing against pkgutil --pkgs output.

"installer_url": "https://dl.google.com/chrome-remote-desktop/chromeremotedesktop.dmg",
"install_script_ref": "76c02a92",
"uninstall_script_ref": "1a1047b8",
"uninstall_script_ref": "82670631",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Preserve plist cleanup when wildcard expansion finds no loaded job.

Line 20 expands wildcard labels only from launchctl list and returns when that list is empty. For wildcard patterns, this skips plist removal entirely, so unloaded matching LaunchAgents/Daemons can remain and reappear on next bootstrap/reboot. Keep a separate glob-based plist cleanup for the original wildcard, or derive labels from matching plist basenames as well; apply it in the shared uninstall template and regenerate these manifests.

Also applies to: 20-20

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ee/maintained-apps/outputs/chrome-remote-desktop-host/darwin.json` at line
11, The shared uninstall template is skipping plist cleanup when wildcard
expansion via `launchctl list` finds no loaded jobs, so unloaded matching
LaunchAgents/Daemons can be left behind. Update the uninstall logic around the
wildcard handling in the shared template to also perform plist cleanup for the
original wildcard pattern, either by globbing matching plist basenames or by
keeping a separate cleanup path when no loaded job labels are returned, then
regenerate the affected manifest that references `uninstall_script_ref`.

"refs": {
"917a2397": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nTMPDIR=$(dirname \"$(realpath \"$INSTALLER_PATH\")\")\n# functions\n\nquit_and_track_application() {\n local bundle_id=\"$1\"\n local var_name=\"APP_WAS_RUNNING_$(echo \"$bundle_id\" | tr '.-' '__')\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e \"application id \\\"$bundle_id\\\" is running\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n eval \"export $var_name=0\"\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n eval \"export $var_name=0\"\n return\n fi\n\n # App was running, mark it for relaunch\n eval \"export $var_name=1\"\n echo \"Application '$bundle_id' was running; will relaunch after installation.\"\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" >/dev/null 2>&1; then\n if ! pgrep -f \"$bundle_id\" >/dev/null 2>&1; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n fi\n sleep 1\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n fi\n}\n\n\nrelaunch_application() {\n local bundle_id=\"$1\"\n local var_name=\"APP_WAS_RUNNING_$(echo \"$bundle_id\" | tr '.-' '__')\"\n local was_running\n\n # Check if the app was running before installation\n eval \"was_running=\\$$var_name\"\n if [[ \"$was_running\" != \"1\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping relaunching application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Relaunching application '$bundle_id'...\"\n\n # Launch the app in the logged-in user's GUI session. Apps launched by root\n # won't register with the user's Dock/GUI, so run 'open' as the console user.\n # Use 'launchctl asuser' to bootstrap into the console user's Mach namespace\n # and GUI session — 'sudo -u' alone doesn't do this, which can cause\n # LSOpenURLsWithRole() failures even when 'open' exits 0.\n local open_status=0\n if [[ $EUID -eq 0 ]]; then\n local console_uid\n console_uid=$(id -u \"$console_user\")\n /bin/launchctl asuser \"$console_uid\" sudo -u \"$console_user\" open -b \"$bundle_id\" >/dev/null 2>&1 || open_status=$?\n else\n open -b \"$bundle_id\" >/dev/null 2>&1 || open_status=$?\n fi\n\n if [[ $open_status -eq 0 ]]; then\n echo \"Application '$bundle_id' relaunched successfully.\"\n else\n echo \"Failed to relaunch application '$bundle_id'.\"\n fi\n}\n\n\n# extract contents\nMOUNT_POINT=$(mktemp -d /tmp/dmg_mount_XXXXXX)\nyes | hdiutil attach -plist -nobrowse -readonly -mountpoint \"$MOUNT_POINT\" \"$INSTALLER_PATH\" || exit 1\nsudo cp -R \"$MOUNT_POINT\"/* \"$TMPDIR\"\nhdiutil detach \"$MOUNT_POINT\" || true\n# copy to the applications folder\nquit_and_track_application 'com.exafunction.windsurf'\nif [ -d \"$APPDIR/Devin.app\" ]; then\n\tsudo mv \"$APPDIR/Devin.app\" \"$TMPDIR/Devin.app.bkp\"\nfi\nsudo cp -R \"$TMPDIR/Devin.app\" \"$APPDIR\"\nrelaunch_application 'com.exafunction.windsurf'\n",
"cb4ce7dc": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\nquit_application() {\n local bundle_id=\"$1\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e \"application id \\\"$bundle_id\\\" is running\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" >/dev/null 2>&1; then\n if ! pgrep -f \"$bundle_id\" >/dev/null 2>&1; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n fi\n sleep 1\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n fi\n}\n\n\nremove_launchctl_service() {\n local service=\"$1\"\n local booleans=(\"true\" \"false\")\n local plist_status\n local paths\n local should_sudo\n\n echo \"Removing launchctl service ${service}\"\n\n for should_sudo in \"${booleans[@]}\"; do\n plist_status=$(launchctl list \"${service}\" 2>/dev/null)\n\n if [[ $plist_status == \\{* ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo launchctl remove \"${service}\"\n else\n launchctl remove \"${service}\"\n fi\n sleep 1\n fi\n\n paths=(\n \"/Library/LaunchAgents/${service}.plist\"\n \"/Library/LaunchDaemons/${service}.plist\"\n )\n\n # if not using sudo, prepend the home directory to the paths\n if [[ $should_sudo == \"false\" ]]; then\n for i in \"${!paths[@]}\"; do\n paths[i]=\"${HOME}${paths[i]}\"\n done\n fi\n\n for path in \"${paths[@]}\"; do\n if [[ -e \"$path\" ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo rm -f -- \"$path\"\n else\n rm -f -- \"$path\"\n fi\n fi\n done\n done\n}\n\ntrash() {\n local logged_in_user=\"$1\"\n local target_file=\"$2\"\n local timestamp=\"$(date +%Y-%m-%d-%s)\"\n local rand=\"$(jot -r 1 0 99999)\"\n\n # replace ~ with /Users/$logged_in_user\n if [[ \"$target_file\" == ~* ]]; then\n target_file=\"/Users/$logged_in_user${target_file:1}\"\n fi\n\n local trash=\"/Users/$logged_in_user/.Trash\"\n\n # If the target contains glob characters, expand it and move each match.\n if [[ \"$target_file\" == *[*?[]* ]]; then\n local file file_name\n local matched=false\n local i=0\n # compgen -G expands the (quoted) pattern itself, so paths containing\n # spaces glob correctly; reading line by line keeps each match intact.\n while IFS= read -r file; do\n [[ -n \"$file\" ]] || continue\n [[ -e \"$file\" || -L \"$file\" ]] || continue\n matched=true\n i=$((i + 1))\n file_name=\"$(basename \"$file\")\"\n echo \"removing $file.\"\n # The per-match counter keeps matches that share a basename from\n # overwriting each other in the trash.\n mv -f \"$file\" \"$trash/${file_name}_${timestamp}_${rand}_${i}\"\n done < <(compgen -G \"$target_file\" 2>/dev/null)\n if [[ \"$matched\" == false ]]; then\n echo \"$target_file doesn't exist.\"\n fi\n return\n fi\n\n local file_name=\"$(basename \"${target_file}\")\"\n\n if [[ -e \"$target_file\" ]]; then\n echo \"removing $target_file.\"\n mv -f \"$target_file\" \"$trash/${file_name}_${timestamp}_${rand}\"\n else\n echo \"$target_file doesn't exist.\"\n fi\n}\n\nremove_launchctl_service 'com.exafunction.windsurf.ShipIt'\nquit_application 'com.exafunction.windsurf'\nsudo rm -rf \"$APPDIR/Devin.app\"\nsudo rmdir '~/.codeium/windsurf'\ntrash $LOGGED_IN_USER '~/.devin'\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.exafunction.windsurf.sfl*'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Devin'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.exafunction.windsurf'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.exafunction.windsurf.ShipIt'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.exafunction.windsurf'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.exafunction.windsurf.plist'\n"
"4fdaa2d1": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\nquit_application() {\n local bundle_id=\"$1\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e \"application id \\\"$bundle_id\\\" is running\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" >/dev/null 2>&1; then\n if ! pgrep -f \"$bundle_id\" >/dev/null 2>&1; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n fi\n sleep 1\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n fi\n}\n\n\nremove_launchctl_service() {\n local service=\"$1\"\n local booleans=(\"true\" \"false\")\n local plist_status\n local paths\n local should_sudo\n\n echo \"Removing launchctl service ${service}\"\n\n # A wildcard label can't be used with launchctl or as a plist name, so expand\n # it to the labels of currently loaded services that match the pattern.\n local services=(\"$service\")\n if [[ \"$service\" == *\"*\"* ]]; then\n local regex\n # Escape regex metacharacters, turn '*' into '.*', and anchor the pattern so\n # it matches a full label rather than a substring.\n regex=$(printf '%s' \"$service\" | sed -e 's/[][(){}.^$+?|\\\\]/\\\\&/g' -e 's/\\*/.*/g')\n regex=\"^${regex}$\"\n services=()\n local id\n # Match every loaded job by label regardless of PID; launchctl list reports\n # loaded-but-not-running jobs with a \"-\" in the PID column.\n while read -r _ _ id; do\n [[ \"$id\" =~ $regex ]] && services+=(\"$id\")\n done < <(launchctl list 2>/dev/null | tail -n +2)\n if [[ ${#services[@]} -eq 0 ]]; then\n echo \"No loaded launchctl service matches ${service}\"\n return\n fi\n fi\n\n local service_label\n for service_label in \"${services[@]}\"; do\n for should_sudo in \"${booleans[@]}\"; do\n plist_status=$(launchctl list \"${service_label}\" 2>/dev/null)\n\n if [[ $plist_status == \\{* ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo launchctl remove \"${service_label}\"\n else\n launchctl remove \"${service_label}\"\n fi\n sleep 1\n fi\n\n paths=(\n \"/Library/LaunchAgents/${service_label}.plist\"\n \"/Library/LaunchDaemons/${service_label}.plist\"\n )\n\n # if not using sudo, prepend the home directory to the paths\n if [[ $should_sudo == \"false\" ]]; then\n for i in \"${!paths[@]}\"; do\n paths[i]=\"${HOME}${paths[i]}\"\n done\n fi\n\n for path in \"${paths[@]}\"; do\n if [[ -e \"$path\" ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo rm -f -- \"$path\"\n else\n rm -f -- \"$path\"\n fi\n fi\n done\n done\n done\n}\n\ntrash() {\n local logged_in_user=\"$1\"\n local target_file=\"$2\"\n local timestamp=\"$(date +%Y-%m-%d-%s)\"\n local rand=\"$(jot -r 1 0 99999)\"\n\n # replace ~ with /Users/$logged_in_user\n if [[ \"$target_file\" == ~* ]]; then\n target_file=\"/Users/$logged_in_user${target_file:1}\"\n fi\n\n local trash=\"/Users/$logged_in_user/.Trash\"\n\n # If the target contains glob characters, expand it and move each match.\n if [[ \"$target_file\" == *[*?[]* ]]; then\n local file file_name\n local matched=false\n local i=0\n # compgen -G expands the (quoted) pattern itself, so paths containing\n # spaces glob correctly; reading line by line keeps each match intact.\n while IFS= read -r file; do\n [[ -n \"$file\" ]] || continue\n [[ -e \"$file\" || -L \"$file\" ]] || continue\n matched=true\n i=$((i + 1))\n file_name=\"$(basename \"$file\")\"\n echo \"removing $file.\"\n # The per-match counter keeps matches that share a basename from\n # overwriting each other in the trash.\n mv -f \"$file\" \"$trash/${file_name}_${timestamp}_${rand}_${i}\"\n done < <(compgen -G \"$target_file\" 2>/dev/null)\n if [[ \"$matched\" == false ]]; then\n echo \"$target_file doesn't exist.\"\n fi\n return\n fi\n\n local file_name=\"$(basename \"${target_file}\")\"\n\n if [[ -e \"$target_file\" ]]; then\n echo \"removing $target_file.\"\n mv -f \"$target_file\" \"$trash/${file_name}_${timestamp}_${rand}\"\n else\n echo \"$target_file doesn't exist.\"\n fi\n}\n\nremove_launchctl_service 'com.exafunction.windsurf.ShipIt'\nquit_application 'com.exafunction.windsurf'\nsudo rm -rf \"$APPDIR/Devin.app\"\nsudo rmdir '~/.codeium/windsurf'\ntrash $LOGGED_IN_USER '~/.devin'\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.exafunction.windsurf.sfl*'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Devin'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.exafunction.windsurf'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.exafunction.windsurf.ShipIt'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.exafunction.windsurf'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.exafunction.windsurf.plist'\n",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Don't return before cleaning wildcard plist files.

Line 19 exits when no currently loaded launchctl label matches the wildcard. That leaves matching LaunchAgent/LaunchDaemon plist files behind whenever the job is installed but unloaded, which breaks the wildcard uninstall behavior this rollout is adding. Collect matching plist basenames from the LaunchAgents/LaunchDaemons paths before the empty-match return, then de-dupe services.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ee/maintained-apps/outputs/devin-desktop/darwin.json` at line 19, The
wildcard cleanup in remove_launchctl_service returns too early when launchctl
has no loaded matches, leaving installed but unloaded LaunchAgent/LaunchDaemon
plist files behind. Update the service expansion logic to scan the LaunchAgents
and LaunchDaemons plist paths for matching basenames before deciding whether to
return, then merge those results into services and de-duplicate them so wildcard
uninstall still removes unloaded plists.

"3b5481a7": "#!/bin/bash\n\n# variables\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\nexpand_pkgid_and_map() {\n local PKGID=\"$1\"\n local FUNC=\"$2\"\n if [[ \"$PKGID\" == *\"*\" ]]; then\n local prefix=\"${PKGID%\\*}\"\n echo \"Expanding wildcard for PKGID: $PKGID\"\n for receipt in $(pkgutil --pkgs | grep \"^${prefix}\"); do\n echo \"Processing $receipt\"\n \"$FUNC\" \"$receipt\"\n done\n else\n \"$FUNC\" \"$PKGID\"\n fi\n}\n\nforget_pkg() {\n local PKGID=\"$1\"\n expand_pkgid_and_map \"$PKGID\" forget_receipt\n}\n\nforget_receipt() {\n local PKGID=\"$1\"\n sudo pkgutil --forget \"$PKGID\"\n}\n\nquit_application() {\n local bundle_id=\"$1\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e \"application id \\\"$bundle_id\\\" is running\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" >/dev/null 2>&1; then\n if ! pgrep -f \"$bundle_id\" >/dev/null 2>&1; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n fi\n sleep 1\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n fi\n}\n\n\nremove_launchctl_service() {\n local service=\"$1\"\n local booleans=(\"true\" \"false\")\n local plist_status\n local paths\n local should_sudo\n\n echo \"Removing launchctl service ${service}\"\n\n for should_sudo in \"${booleans[@]}\"; do\n plist_status=$(launchctl list \"${service}\" 2>/dev/null)\n\n if [[ $plist_status == \\{* ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo launchctl remove \"${service}\"\n else\n launchctl remove \"${service}\"\n fi\n sleep 1\n fi\n\n paths=(\n \"/Library/LaunchAgents/${service}.plist\"\n \"/Library/LaunchDaemons/${service}.plist\"\n )\n\n # if not using sudo, prepend the home directory to the paths\n if [[ $should_sudo == \"false\" ]]; then\n for i in \"${!paths[@]}\"; do\n paths[i]=\"${HOME}${paths[i]}\"\n done\n fi\n\n for path in \"${paths[@]}\"; do\n if [[ -e \"$path\" ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo rm -f -- \"$path\"\n else\n rm -f -- \"$path\"\n fi\n fi\n done\n done\n}\n\nremove_pkg_files() {\n local PKGID=\"$1\"\n expand_pkgid_and_map \"$PKGID\" remove_receipt_files\n}\n\nremove_receipt_files() {\n local PKGID=\"$1\"\n local PKGINFO VOLUME INSTALL_LOCATION FULL_INSTALL_LOCATION\n\n echo \"pkgutil --pkg-info-plist \\\"$PKGID\\\"\"\n PKGINFO=$(pkgutil --pkg-info-plist \"$PKGID\")\n VOLUME=$(echo \"$PKGINFO\" | awk '/<key>volume<\\/key>/ {getline; gsub(/.*<string>|<\\/string>.*/, \"\"); print}')\n INSTALL_LOCATION=$(echo \"$PKGINFO\" | awk '/<key>install-location<\\/key>/ {getline; gsub(/.*<string>|<\\/string>.*/, \"\"); print}')\n\n if [ -z \"$INSTALL_LOCATION\" ] || [ \"$INSTALL_LOCATION\" = \"/\" ]; then\n FULL_INSTALL_LOCATION=\"$VOLUME\"\n else\n FULL_INSTALL_LOCATION=\"$VOLUME/$INSTALL_LOCATION\"\n FULL_INSTALL_LOCATION=$(echo \"$FULL_INSTALL_LOCATION\" | sed 's|//|/|g')\n fi\n\n echo \"sudo pkgutil --only-files --files \\\"$PKGID\\\" | sed \\\"s|^|${FULL_INSTALL_LOCATION}/|\\\" | tr '\\\\\\\\n' '\\\\\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\"\n sudo pkgutil --only-files --files \"$PKGID\" | sed \"s|^|/${INSTALL_LOCATION}/|\" | tr '\\n' '\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\n\n echo \"sudo pkgutil --only-dirs --files \\\"$PKGID\\\" | sed \\\"s|^|${FULL_INSTALL_LOCATION}/|\\\" | grep '\\\\.app$' | tr '\\\\\\\\n' '\\\\\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\"\n sudo pkgutil --only-dirs --files \"$PKGID\" | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" | grep '\\.app$' | tr '\\n' '\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\n\n root_app_dir=$(\n sudo pkgutil --only-dirs --files \"$PKGID\" \\\n | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" \\\n | grep 'Applications' \\\n | awk '{ print length, $0 }' \\\n | sort -n \\\n | head -n1 \\\n | cut -d' ' -f2-\n )\n if [ -n \"$root_app_dir\" ]; then\n echo \"sudo rmdir -p \\\"$root_app_dir\\\" 2>/dev/null || :\"\n sudo rmdir -p \"$root_app_dir\" 2>/dev/null || :\n fi\n}\n\ntrash() {\n local logged_in_user=\"$1\"\n local target_file=\"$2\"\n local timestamp=\"$(date +%Y-%m-%d-%s)\"\n local rand=\"$(jot -r 1 0 99999)\"\n\n # replace ~ with /Users/$logged_in_user\n if [[ \"$target_file\" == ~* ]]; then\n target_file=\"/Users/$logged_in_user${target_file:1}\"\n fi\n\n local trash=\"/Users/$logged_in_user/.Trash\"\n\n # If the target contains glob characters, expand it and move each match.\n if [[ \"$target_file\" == *[*?[]* ]]; then\n local file file_name\n local matched=false\n local i=0\n # compgen -G expands the (quoted) pattern itself, so paths containing\n # spaces glob correctly; reading line by line keeps each match intact.\n while IFS= read -r file; do\n [[ -n \"$file\" ]] || continue\n [[ -e \"$file\" || -L \"$file\" ]] || continue\n matched=true\n i=$((i + 1))\n file_name=\"$(basename \"$file\")\"\n echo \"removing $file.\"\n # The per-match counter keeps matches that share a basename from\n # overwriting each other in the trash.\n mv -f \"$file\" \"$trash/${file_name}_${timestamp}_${rand}_${i}\"\n done < <(compgen -G \"$target_file\" 2>/dev/null)\n if [[ \"$matched\" == false ]]; then\n echo \"$target_file doesn't exist.\"\n fi\n return\n fi\n\n local file_name=\"$(basename \"${target_file}\")\"\n\n if [[ -e \"$target_file\" ]]; then\n echo \"removing $target_file.\"\n mv -f \"$target_file\" \"$trash/${file_name}_${timestamp}_${rand}\"\n else\n echo \"$target_file doesn't exist.\"\n fi\n}\n\nremove_launchctl_service '73YQY62QM3.com.displaylink.DisplayLinkAPServer'\nremove_launchctl_service 'com.displaylink.displaylinkmanager'\nremove_launchctl_service 'com.displaylink.useragent'\nremove_launchctl_service 'com.displaylink.useragent-prelogin'\nremove_launchctl_service 'com.displaylink.XpcService'\nquit_application 'DisplayLinkUserAgent'\nremove_pkg_files 'com.displaylink.*'\nforget_pkg 'com.displaylink.*'\nsudo rm -rf '/Applications/DisplayLink'\nsudo rm -rf '/Library/LaunchAgents/com.displaylink.useragent-prelogin.plist'\nsudo rm -rf '/Library/LaunchAgents/com.displaylink.useragent.plist'\nsudo rm -rf '/Library/LaunchDaemons/com.displaylink.displaylinkmanager.plist'\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/73YQY62QM3.com.displaylink.DisplayLinkShared'\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/com.displaylink.DisplayLinkUserAgent'\ntrash $LOGGED_IN_USER '~/Library/Containers/com.displaylink.DisplayLinkUserAgent'\ntrash $LOGGED_IN_USER '~/Library/Group Containers/73YQY62QM3.com.displaylink.DisplayLinkShared'\n",
"4d8387b8": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nTMPDIR=$(dirname \"$(realpath \"$INSTALLER_PATH\")\")\n# functions\n\nquit_and_track_application() {\n local bundle_id=\"$1\"\n local var_name=\"APP_WAS_RUNNING_$(echo \"$bundle_id\" | tr '.-' '__')\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e \"application id \\\"$bundle_id\\\" is running\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n eval \"export $var_name=0\"\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n eval \"export $var_name=0\"\n return\n fi\n\n # App was running, mark it for relaunch\n eval \"export $var_name=1\"\n echo \"Application '$bundle_id' was running; will relaunch after installation.\"\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" >/dev/null 2>&1; then\n if ! pgrep -f \"$bundle_id\" >/dev/null 2>&1; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n fi\n sleep 1\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n fi\n}\n\n\nrelaunch_application() {\n local bundle_id=\"$1\"\n local var_name=\"APP_WAS_RUNNING_$(echo \"$bundle_id\" | tr '.-' '__')\"\n local was_running\n\n # Check if the app was running before installation\n eval \"was_running=\\$$var_name\"\n if [[ \"$was_running\" != \"1\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping relaunching application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Relaunching application '$bundle_id'...\"\n\n # Launch the app in the logged-in user's GUI session. Apps launched by root\n # won't register with the user's Dock/GUI, so run 'open' as the console user.\n # Use 'launchctl asuser' to bootstrap into the console user's Mach namespace\n # and GUI session — 'sudo -u' alone doesn't do this, which can cause\n # LSOpenURLsWithRole() failures even when 'open' exits 0.\n local open_status=0\n if [[ $EUID -eq 0 ]]; then\n local console_uid\n console_uid=$(id -u \"$console_user\")\n /bin/launchctl asuser \"$console_uid\" sudo -u \"$console_user\" open -b \"$bundle_id\" >/dev/null 2>&1 || open_status=$?\n else\n open -b \"$bundle_id\" >/dev/null 2>&1 || open_status=$?\n fi\n\n if [[ $open_status -eq 0 ]]; then\n echo \"Application '$bundle_id' relaunched successfully.\"\n else\n echo \"Failed to relaunch application '$bundle_id'.\"\n fi\n}\n\n\n# install pkg files\nquit_and_track_application 'com.displaylink.DisplayLinkUserAgent'\nsudo installer -pkg \"$TMPDIR/DisplayLink Manager Graphics Connectivity16.1-EXE.pkg\" -target /\nrelaunch_application 'com.displaylink.DisplayLinkUserAgent'\n"
"4d8387b8": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nTMPDIR=$(dirname \"$(realpath \"$INSTALLER_PATH\")\")\n# functions\n\nquit_and_track_application() {\n local bundle_id=\"$1\"\n local var_name=\"APP_WAS_RUNNING_$(echo \"$bundle_id\" | tr '.-' '__')\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e \"application id \\\"$bundle_id\\\" is running\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n eval \"export $var_name=0\"\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n eval \"export $var_name=0\"\n return\n fi\n\n # App was running, mark it for relaunch\n eval \"export $var_name=1\"\n echo \"Application '$bundle_id' was running; will relaunch after installation.\"\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" >/dev/null 2>&1; then\n if ! pgrep -f \"$bundle_id\" >/dev/null 2>&1; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n fi\n sleep 1\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n fi\n}\n\n\nrelaunch_application() {\n local bundle_id=\"$1\"\n local var_name=\"APP_WAS_RUNNING_$(echo \"$bundle_id\" | tr '.-' '__')\"\n local was_running\n\n # Check if the app was running before installation\n eval \"was_running=\\$$var_name\"\n if [[ \"$was_running\" != \"1\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping relaunching application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Relaunching application '$bundle_id'...\"\n\n # Launch the app in the logged-in user's GUI session. Apps launched by root\n # won't register with the user's Dock/GUI, so run 'open' as the console user.\n # Use 'launchctl asuser' to bootstrap into the console user's Mach namespace\n # and GUI session — 'sudo -u' alone doesn't do this, which can cause\n # LSOpenURLsWithRole() failures even when 'open' exits 0.\n local open_status=0\n if [[ $EUID -eq 0 ]]; then\n local console_uid\n console_uid=$(id -u \"$console_user\")\n /bin/launchctl asuser \"$console_uid\" sudo -u \"$console_user\" open -b \"$bundle_id\" >/dev/null 2>&1 || open_status=$?\n else\n open -b \"$bundle_id\" >/dev/null 2>&1 || open_status=$?\n fi\n\n if [[ $open_status -eq 0 ]]; then\n echo \"Application '$bundle_id' relaunched successfully.\"\n else\n echo \"Failed to relaunch application '$bundle_id'.\"\n fi\n}\n\n\n# install pkg files\nquit_and_track_application 'com.displaylink.DisplayLinkUserAgent'\nsudo installer -pkg \"$TMPDIR/DisplayLink Manager Graphics Connectivity16.1-EXE.pkg\" -target /\nrelaunch_application 'com.displaylink.DisplayLinkUserAgent'\n",
"ada69274": "#!/bin/bash\n\n# variables\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\nexpand_pkgid_and_map() {\n local PKGID=\"$1\"\n local FUNC=\"$2\"\n if [[ \"$PKGID\" == *\"*\" ]]; then\n local prefix=\"${PKGID%\\*}\"\n echo \"Expanding wildcard for PKGID: $PKGID\"\n for receipt in $(pkgutil --pkgs | grep \"^${prefix}\"); do\n echo \"Processing $receipt\"\n \"$FUNC\" \"$receipt\"\n done\n else\n \"$FUNC\" \"$PKGID\"\n fi\n}\n\nforget_pkg() {\n local PKGID=\"$1\"\n expand_pkgid_and_map \"$PKGID\" forget_receipt\n}\n\nforget_receipt() {\n local PKGID=\"$1\"\n sudo pkgutil --forget \"$PKGID\"\n}\n\nquit_application() {\n local bundle_id=\"$1\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e \"application id \\\"$bundle_id\\\" is running\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" >/dev/null 2>&1; then\n if ! pgrep -f \"$bundle_id\" >/dev/null 2>&1; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n fi\n sleep 1\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n fi\n}\n\n\nremove_launchctl_service() {\n local service=\"$1\"\n local booleans=(\"true\" \"false\")\n local plist_status\n local paths\n local should_sudo\n\n echo \"Removing launchctl service ${service}\"\n\n # A wildcard label can't be used with launchctl or as a plist name, so expand\n # it to the labels of currently loaded services that match the pattern.\n local services=(\"$service\")\n if [[ \"$service\" == *\"*\"* ]]; then\n local regex\n # Escape regex metacharacters, turn '*' into '.*', and anchor the pattern so\n # it matches a full label rather than a substring.\n regex=$(printf '%s' \"$service\" | sed -e 's/[][(){}.^$+?|\\\\]/\\\\&/g' -e 's/\\*/.*/g')\n regex=\"^${regex}$\"\n services=()\n local id\n # Match every loaded job by label regardless of PID; launchctl list reports\n # loaded-but-not-running jobs with a \"-\" in the PID column.\n while read -r _ _ id; do\n [[ \"$id\" =~ $regex ]] && services+=(\"$id\")\n done < <(launchctl list 2>/dev/null | tail -n +2)\n if [[ ${#services[@]} -eq 0 ]]; then\n echo \"No loaded launchctl service matches ${service}\"\n return\n fi\n fi\n\n local service_label\n for service_label in \"${services[@]}\"; do\n for should_sudo in \"${booleans[@]}\"; do\n plist_status=$(launchctl list \"${service_label}\" 2>/dev/null)\n\n if [[ $plist_status == \\{* ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo launchctl remove \"${service_label}\"\n else\n launchctl remove \"${service_label}\"\n fi\n sleep 1\n fi\n\n paths=(\n \"/Library/LaunchAgents/${service_label}.plist\"\n \"/Library/LaunchDaemons/${service_label}.plist\"\n )\n\n # if not using sudo, prepend the home directory to the paths\n if [[ $should_sudo == \"false\" ]]; then\n for i in \"${!paths[@]}\"; do\n paths[i]=\"${HOME}${paths[i]}\"\n done\n fi\n\n for path in \"${paths[@]}\"; do\n if [[ -e \"$path\" ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo rm -f -- \"$path\"\n else\n rm -f -- \"$path\"\n fi\n fi\n done\n done\n done\n}\n\nremove_pkg_files() {\n local PKGID=\"$1\"\n expand_pkgid_and_map \"$PKGID\" remove_receipt_files\n}\n\nremove_receipt_files() {\n local PKGID=\"$1\"\n local PKGINFO VOLUME INSTALL_LOCATION FULL_INSTALL_LOCATION\n\n echo \"pkgutil --pkg-info-plist \\\"$PKGID\\\"\"\n PKGINFO=$(pkgutil --pkg-info-plist \"$PKGID\")\n VOLUME=$(echo \"$PKGINFO\" | awk '/<key>volume<\\/key>/ {getline; gsub(/.*<string>|<\\/string>.*/, \"\"); print}')\n INSTALL_LOCATION=$(echo \"$PKGINFO\" | awk '/<key>install-location<\\/key>/ {getline; gsub(/.*<string>|<\\/string>.*/, \"\"); print}')\n\n if [ -z \"$INSTALL_LOCATION\" ] || [ \"$INSTALL_LOCATION\" = \"/\" ]; then\n FULL_INSTALL_LOCATION=\"$VOLUME\"\n else\n FULL_INSTALL_LOCATION=\"$VOLUME/$INSTALL_LOCATION\"\n FULL_INSTALL_LOCATION=$(echo \"$FULL_INSTALL_LOCATION\" | sed 's|//|/|g')\n fi\n\n echo \"sudo pkgutil --only-files --files \\\"$PKGID\\\" | sed \\\"s|^|${FULL_INSTALL_LOCATION}/|\\\" | tr '\\\\\\\\n' '\\\\\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\"\n sudo pkgutil --only-files --files \"$PKGID\" | sed \"s|^|/${INSTALL_LOCATION}/|\" | tr '\\n' '\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\n\n echo \"sudo pkgutil --only-dirs --files \\\"$PKGID\\\" | sed \\\"s|^|${FULL_INSTALL_LOCATION}/|\\\" | grep '\\\\.app$' | tr '\\\\\\\\n' '\\\\\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\"\n sudo pkgutil --only-dirs --files \"$PKGID\" | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" | grep '\\.app$' | tr '\\n' '\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\n\n root_app_dir=$(\n sudo pkgutil --only-dirs --files \"$PKGID\" \\\n | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" \\\n | grep 'Applications' \\\n | awk '{ print length, $0 }' \\\n | sort -n \\\n | head -n1 \\\n | cut -d' ' -f2-\n )\n if [ -n \"$root_app_dir\" ]; then\n echo \"sudo rmdir -p \\\"$root_app_dir\\\" 2>/dev/null || :\"\n sudo rmdir -p \"$root_app_dir\" 2>/dev/null || :\n fi\n}\n\ntrash() {\n local logged_in_user=\"$1\"\n local target_file=\"$2\"\n local timestamp=\"$(date +%Y-%m-%d-%s)\"\n local rand=\"$(jot -r 1 0 99999)\"\n\n # replace ~ with /Users/$logged_in_user\n if [[ \"$target_file\" == ~* ]]; then\n target_file=\"/Users/$logged_in_user${target_file:1}\"\n fi\n\n local trash=\"/Users/$logged_in_user/.Trash\"\n\n # If the target contains glob characters, expand it and move each match.\n if [[ \"$target_file\" == *[*?[]* ]]; then\n local file file_name\n local matched=false\n local i=0\n # compgen -G expands the (quoted) pattern itself, so paths containing\n # spaces glob correctly; reading line by line keeps each match intact.\n while IFS= read -r file; do\n [[ -n \"$file\" ]] || continue\n [[ -e \"$file\" || -L \"$file\" ]] || continue\n matched=true\n i=$((i + 1))\n file_name=\"$(basename \"$file\")\"\n echo \"removing $file.\"\n # The per-match counter keeps matches that share a basename from\n # overwriting each other in the trash.\n mv -f \"$file\" \"$trash/${file_name}_${timestamp}_${rand}_${i}\"\n done < <(compgen -G \"$target_file\" 2>/dev/null)\n if [[ \"$matched\" == false ]]; then\n echo \"$target_file doesn't exist.\"\n fi\n return\n fi\n\n local file_name=\"$(basename \"${target_file}\")\"\n\n if [[ -e \"$target_file\" ]]; then\n echo \"removing $target_file.\"\n mv -f \"$target_file\" \"$trash/${file_name}_${timestamp}_${rand}\"\n else\n echo \"$target_file doesn't exist.\"\n fi\n}\n\nremove_launchctl_service '73YQY62QM3.com.displaylink.DisplayLinkAPServer'\nremove_launchctl_service 'com.displaylink.displaylinkmanager'\nremove_launchctl_service 'com.displaylink.useragent'\nremove_launchctl_service 'com.displaylink.useragent-prelogin'\nremove_launchctl_service 'com.displaylink.XpcService'\nquit_application 'DisplayLinkUserAgent'\nremove_pkg_files 'com.displaylink.*'\nforget_pkg 'com.displaylink.*'\nsudo rm -rf '/Applications/DisplayLink'\nsudo rm -rf '/Library/LaunchAgents/com.displaylink.useragent-prelogin.plist'\nsudo rm -rf '/Library/LaunchAgents/com.displaylink.useragent.plist'\nsudo rm -rf '/Library/LaunchDaemons/com.displaylink.displaylinkmanager.plist'\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/73YQY62QM3.com.displaylink.DisplayLinkShared'\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/com.displaylink.DisplayLinkUserAgent'\ntrash $LOGGED_IN_USER '~/Library/Containers/com.displaylink.DisplayLinkUserAgent'\ntrash $LOGGED_IN_USER '~/Library/Group Containers/73YQY62QM3.com.displaylink.DisplayLinkShared'\n"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Use fixed-prefix matching for package wildcards.

Line 20 expands com.displaylink.* with grep "^${prefix}", so the dots in com.displaylink. are regex wildcards. That can match unrelated receipts and feed them into remove_pkg_files, causing unintended file removal. Use a fixed-string prefix check.

Safer prefix expansion
-    for receipt in $(pkgutil --pkgs | grep "^${prefix}"); do
+    while IFS= read -r receipt; do
       echo "Processing $receipt"
       "$FUNC" "$receipt"
-    done
+    done < <(pkgutil --pkgs | awk -v prefix="$prefix" 'index($0, prefix) == 1')
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"ada69274": "#!/bin/bash\n\n# variables\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\nexpand_pkgid_and_map() {\n local PKGID=\"$1\"\n local FUNC=\"$2\"\n if [[ \"$PKGID\" == *\"*\" ]]; then\n local prefix=\"${PKGID%\\*}\"\n echo \"Expanding wildcard for PKGID: $PKGID\"\n for receipt in $(pkgutil --pkgs | grep \"^${prefix}\"); do\n echo \"Processing $receipt\"\n \"$FUNC\" \"$receipt\"\n done\n else\n \"$FUNC\" \"$PKGID\"\n fi\n}\n\nforget_pkg() {\n local PKGID=\"$1\"\n expand_pkgid_and_map \"$PKGID\" forget_receipt\n}\n\nforget_receipt() {\n local PKGID=\"$1\"\n sudo pkgutil --forget \"$PKGID\"\n}\n\nquit_application() {\n local bundle_id=\"$1\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e \"application id \\\"$bundle_id\\\" is running\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" >/dev/null 2>&1; then\n if ! pgrep -f \"$bundle_id\" >/dev/null 2>&1; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n fi\n sleep 1\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n fi\n}\n\n\nremove_launchctl_service() {\n local service=\"$1\"\n local booleans=(\"true\" \"false\")\n local plist_status\n local paths\n local should_sudo\n\n echo \"Removing launchctl service ${service}\"\n\n # A wildcard label can't be used with launchctl or as a plist name, so expand\n # it to the labels of currently loaded services that match the pattern.\n local services=(\"$service\")\n if [[ \"$service\" == *\"*\"* ]]; then\n local regex\n # Escape regex metacharacters, turn '*' into '.*', and anchor the pattern so\n # it matches a full label rather than a substring.\n regex=$(printf '%s' \"$service\" | sed -e 's/[][(){}.^$+?|\\\\]/\\\\&/g' -e 's/\\*/.*/g')\n regex=\"^${regex}$\"\n services=()\n local id\n # Match every loaded job by label regardless of PID; launchctl list reports\n # loaded-but-not-running jobs with a \"-\" in the PID column.\n while read -r _ _ id; do\n [[ \"$id\" =~ $regex ]] && services+=(\"$id\")\n done < <(launchctl list 2>/dev/null | tail -n +2)\n if [[ ${#services[@]} -eq 0 ]]; then\n echo \"No loaded launchctl service matches ${service}\"\n return\n fi\n fi\n\n local service_label\n for service_label in \"${services[@]}\"; do\n for should_sudo in \"${booleans[@]}\"; do\n plist_status=$(launchctl list \"${service_label}\" 2>/dev/null)\n\n if [[ $plist_status == \\{* ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo launchctl remove \"${service_label}\"\n else\n launchctl remove \"${service_label}\"\n fi\n sleep 1\n fi\n\n paths=(\n \"/Library/LaunchAgents/${service_label}.plist\"\n \"/Library/LaunchDaemons/${service_label}.plist\"\n )\n\n # if not using sudo, prepend the home directory to the paths\n if [[ $should_sudo == \"false\" ]]; then\n for i in \"${!paths[@]}\"; do\n paths[i]=\"${HOME}${paths[i]}\"\n done\n fi\n\n for path in \"${paths[@]}\"; do\n if [[ -e \"$path\" ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo rm -f -- \"$path\"\n else\n rm -f -- \"$path\"\n fi\n fi\n done\n done\n done\n}\n\nremove_pkg_files() {\n local PKGID=\"$1\"\n expand_pkgid_and_map \"$PKGID\" remove_receipt_files\n}\n\nremove_receipt_files() {\n local PKGID=\"$1\"\n local PKGINFO VOLUME INSTALL_LOCATION FULL_INSTALL_LOCATION\n\n echo \"pkgutil --pkg-info-plist \\\"$PKGID\\\"\"\n PKGINFO=$(pkgutil --pkg-info-plist \"$PKGID\")\n VOLUME=$(echo \"$PKGINFO\" | awk '/<key>volume<\\/key>/ {getline; gsub(/.*<string>|<\\/string>.*/, \"\"); print}')\n INSTALL_LOCATION=$(echo \"$PKGINFO\" | awk '/<key>install-location<\\/key>/ {getline; gsub(/.*<string>|<\\/string>.*/, \"\"); print}')\n\n if [ -z \"$INSTALL_LOCATION\" ] || [ \"$INSTALL_LOCATION\" = \"/\" ]; then\n FULL_INSTALL_LOCATION=\"$VOLUME\"\n else\n FULL_INSTALL_LOCATION=\"$VOLUME/$INSTALL_LOCATION\"\n FULL_INSTALL_LOCATION=$(echo \"$FULL_INSTALL_LOCATION\" | sed 's|//|/|g')\n fi\n\n echo \"sudo pkgutil --only-files --files \\\"$PKGID\\\" | sed \\\"s|^|${FULL_INSTALL_LOCATION}/|\\\" | tr '\\\\\\\\n' '\\\\\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\"\n sudo pkgutil --only-files --files \"$PKGID\" | sed \"s|^|/${INSTALL_LOCATION}/|\" | tr '\\n' '\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\n\n echo \"sudo pkgutil --only-dirs --files \\\"$PKGID\\\" | sed \\\"s|^|${FULL_INSTALL_LOCATION}/|\\\" | grep '\\\\.app$' | tr '\\\\\\\\n' '\\\\\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\"\n sudo pkgutil --only-dirs --files \"$PKGID\" | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" | grep '\\.app$' | tr '\\n' '\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\n\n root_app_dir=$(\n sudo pkgutil --only-dirs --files \"$PKGID\" \\\n | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" \\\n | grep 'Applications' \\\n | awk '{ print length, $0 }' \\\n | sort -n \\\n | head -n1 \\\n | cut -d' ' -f2-\n )\n if [ -n \"$root_app_dir\" ]; then\n echo \"sudo rmdir -p \\\"$root_app_dir\\\" 2>/dev/null || :\"\n sudo rmdir -p \"$root_app_dir\" 2>/dev/null || :\n fi\n}\n\ntrash() {\n local logged_in_user=\"$1\"\n local target_file=\"$2\"\n local timestamp=\"$(date +%Y-%m-%d-%s)\"\n local rand=\"$(jot -r 1 0 99999)\"\n\n # replace ~ with /Users/$logged_in_user\n if [[ \"$target_file\" == ~* ]]; then\n target_file=\"/Users/$logged_in_user${target_file:1}\"\n fi\n\n local trash=\"/Users/$logged_in_user/.Trash\"\n\n # If the target contains glob characters, expand it and move each match.\n if [[ \"$target_file\" == *[*?[]* ]]; then\n local file file_name\n local matched=false\n local i=0\n # compgen -G expands the (quoted) pattern itself, so paths containing\n # spaces glob correctly; reading line by line keeps each match intact.\n while IFS= read -r file; do\n [[ -n \"$file\" ]] || continue\n [[ -e \"$file\" || -L \"$file\" ]] || continue\n matched=true\n i=$((i + 1))\n file_name=\"$(basename \"$file\")\"\n echo \"removing $file.\"\n # The per-match counter keeps matches that share a basename from\n # overwriting each other in the trash.\n mv -f \"$file\" \"$trash/${file_name}_${timestamp}_${rand}_${i}\"\n done < <(compgen -G \"$target_file\" 2>/dev/null)\n if [[ \"$matched\" == false ]]; then\n echo \"$target_file doesn't exist.\"\n fi\n return\n fi\n\n local file_name=\"$(basename \"${target_file}\")\"\n\n if [[ -e \"$target_file\" ]]; then\n echo \"removing $target_file.\"\n mv -f \"$target_file\" \"$trash/${file_name}_${timestamp}_${rand}\"\n else\n echo \"$target_file doesn't exist.\"\n fi\n}\n\nremove_launchctl_service '73YQY62QM3.com.displaylink.DisplayLinkAPServer'\nremove_launchctl_service 'com.displaylink.displaylinkmanager'\nremove_launchctl_service 'com.displaylink.useragent'\nremove_launchctl_service 'com.displaylink.useragent-prelogin'\nremove_launchctl_service 'com.displaylink.XpcService'\nquit_application 'DisplayLinkUserAgent'\nremove_pkg_files 'com.displaylink.*'\nforget_pkg 'com.displaylink.*'\nsudo rm -rf '/Applications/DisplayLink'\nsudo rm -rf '/Library/LaunchAgents/com.displaylink.useragent-prelogin.plist'\nsudo rm -rf '/Library/LaunchAgents/com.displaylink.useragent.plist'\nsudo rm -rf '/Library/LaunchDaemons/com.displaylink.displaylinkmanager.plist'\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/73YQY62QM3.com.displaylink.DisplayLinkShared'\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/com.displaylink.DisplayLinkUserAgent'\ntrash $LOGGED_IN_USER '~/Library/Containers/com.displaylink.DisplayLinkUserAgent'\ntrash $LOGGED_IN_USER '~/Library/Group Containers/73YQY62QM3.com.displaylink.DisplayLinkShared'\n"
expand_pkgid_and_map() {
local PKGID="$1"
local FUNC="$2"
if [[ "$PKGID" == *"*" ]]; then
local prefix="${PKGID%\*}"
echo "Expanding wildcard for PKGID: $PKGID"
while IFS= read -r receipt; do
echo "Processing $receipt"
"$FUNC" "$receipt"
done < <(pkgutil --pkgs | awk -v prefix="$prefix" 'index($0, prefix) == 1')
else
"$FUNC" "$PKGID"
fi
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ee/maintained-apps/outputs/displaylink/darwin.json` at line 20, The wildcard
expansion in expand_pkgid_and_map is using regex matching for package IDs, so a
pattern like com.displaylink.* can accidentally match unrelated receipts. Update
the prefix-matching logic in expand_pkgid_and_map and its callers like
remove_pkg_files/forget_pkg to use a fixed-string prefix check instead of grep
regex behavior. Keep the wildcard expansion behavior, but only select receipts
that truly start with the computed prefix.

"027ac1a3": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\nexpand_pkgid_and_map() {\n local PKGID=\"$1\"\n local FUNC=\"$2\"\n if [[ \"$PKGID\" == *\"*\" ]]; then\n local prefix=\"${PKGID%\\*}\"\n echo \"Expanding wildcard for PKGID: $PKGID\"\n for receipt in $(pkgutil --pkgs | grep \"^${prefix}\"); do\n echo \"Processing $receipt\"\n \"$FUNC\" \"$receipt\"\n done\n else\n \"$FUNC\" \"$PKGID\"\n fi\n}\n\nforget_pkg() {\n local PKGID=\"$1\"\n expand_pkgid_and_map \"$PKGID\" forget_receipt\n}\n\nforget_receipt() {\n local PKGID=\"$1\"\n sudo pkgutil --forget \"$PKGID\"\n}\n\nquit_application() {\n local bundle_id=\"$1\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e \"application id \\\"$bundle_id\\\" is running\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" >/dev/null 2>&1; then\n if ! pgrep -f \"$bundle_id\" >/dev/null 2>&1; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n fi\n sleep 1\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n fi\n}\n\n\nremove_launchctl_service() {\n local service=\"$1\"\n local booleans=(\"true\" \"false\")\n local plist_status\n local paths\n local should_sudo\n\n echo \"Removing launchctl service ${service}\"\n\n for should_sudo in \"${booleans[@]}\"; do\n plist_status=$(launchctl list \"${service}\" 2>/dev/null)\n\n if [[ $plist_status == \\{* ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo launchctl remove \"${service}\"\n else\n launchctl remove \"${service}\"\n fi\n sleep 1\n fi\n\n paths=(\n \"/Library/LaunchAgents/${service}.plist\"\n \"/Library/LaunchDaemons/${service}.plist\"\n )\n\n # if not using sudo, prepend the home directory to the paths\n if [[ $should_sudo == \"false\" ]]; then\n for i in \"${!paths[@]}\"; do\n paths[i]=\"${HOME}${paths[i]}\"\n done\n fi\n\n for path in \"${paths[@]}\"; do\n if [[ -e \"$path\" ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo rm -f -- \"$path\"\n else\n rm -f -- \"$path\"\n fi\n fi\n done\n done\n}\n\nremove_pkg_files() {\n local PKGID=\"$1\"\n expand_pkgid_and_map \"$PKGID\" remove_receipt_files\n}\n\nremove_receipt_files() {\n local PKGID=\"$1\"\n local PKGINFO VOLUME INSTALL_LOCATION FULL_INSTALL_LOCATION\n\n echo \"pkgutil --pkg-info-plist \\\"$PKGID\\\"\"\n PKGINFO=$(pkgutil --pkg-info-plist \"$PKGID\")\n VOLUME=$(echo \"$PKGINFO\" | awk '/<key>volume<\\/key>/ {getline; gsub(/.*<string>|<\\/string>.*/, \"\"); print}')\n INSTALL_LOCATION=$(echo \"$PKGINFO\" | awk '/<key>install-location<\\/key>/ {getline; gsub(/.*<string>|<\\/string>.*/, \"\"); print}')\n\n if [ -z \"$INSTALL_LOCATION\" ] || [ \"$INSTALL_LOCATION\" = \"/\" ]; then\n FULL_INSTALL_LOCATION=\"$VOLUME\"\n else\n FULL_INSTALL_LOCATION=\"$VOLUME/$INSTALL_LOCATION\"\n FULL_INSTALL_LOCATION=$(echo \"$FULL_INSTALL_LOCATION\" | sed 's|//|/|g')\n fi\n\n echo \"sudo pkgutil --only-files --files \\\"$PKGID\\\" | sed \\\"s|^|${FULL_INSTALL_LOCATION}/|\\\" | tr '\\\\\\\\n' '\\\\\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\"\n sudo pkgutil --only-files --files \"$PKGID\" | sed \"s|^|/${INSTALL_LOCATION}/|\" | tr '\\n' '\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\n\n echo \"sudo pkgutil --only-dirs --files \\\"$PKGID\\\" | sed \\\"s|^|${FULL_INSTALL_LOCATION}/|\\\" | grep '\\\\.app$' | tr '\\\\\\\\n' '\\\\\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\"\n sudo pkgutil --only-dirs --files \"$PKGID\" | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" | grep '\\.app$' | tr '\\n' '\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\n\n root_app_dir=$(\n sudo pkgutil --only-dirs --files \"$PKGID\" \\\n | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" \\\n | grep 'Applications' \\\n | awk '{ print length, $0 }' \\\n | sort -n \\\n | head -n1 \\\n | cut -d' ' -f2-\n )\n if [ -n \"$root_app_dir\" ]; then\n echo \"sudo rmdir -p \\\"$root_app_dir\\\" 2>/dev/null || :\"\n sudo rmdir -p \"$root_app_dir\" 2>/dev/null || :\n fi\n}\n\ntrash() {\n local logged_in_user=\"$1\"\n local target_file=\"$2\"\n local timestamp=\"$(date +%Y-%m-%d-%s)\"\n local rand=\"$(jot -r 1 0 99999)\"\n\n # replace ~ with /Users/$logged_in_user\n if [[ \"$target_file\" == ~* ]]; then\n target_file=\"/Users/$logged_in_user${target_file:1}\"\n fi\n\n local trash=\"/Users/$logged_in_user/.Trash\"\n\n # If the target contains glob characters, expand it and move each match.\n if [[ \"$target_file\" == *[*?[]* ]]; then\n local file file_name\n local matched=false\n local i=0\n # compgen -G expands the (quoted) pattern itself, so paths containing\n # spaces glob correctly; reading line by line keeps each match intact.\n while IFS= read -r file; do\n [[ -n \"$file\" ]] || continue\n [[ -e \"$file\" || -L \"$file\" ]] || continue\n matched=true\n i=$((i + 1))\n file_name=\"$(basename \"$file\")\"\n echo \"removing $file.\"\n # The per-match counter keeps matches that share a basename from\n # overwriting each other in the trash.\n mv -f \"$file\" \"$trash/${file_name}_${timestamp}_${rand}_${i}\"\n done < <(compgen -G \"$target_file\" 2>/dev/null)\n if [[ \"$matched\" == false ]]; then\n echo \"$target_file doesn't exist.\"\n fi\n return\n fi\n\n local file_name=\"$(basename \"${target_file}\")\"\n\n if [[ -e \"$target_file\" ]]; then\n echo \"removing $target_file.\"\n mv -f \"$target_file\" \"$trash/${file_name}_${timestamp}_${rand}\"\n else\n echo \"$target_file doesn't exist.\"\n fi\n}\n\nremove_launchctl_service 'com.elgato.WaveLink'\nquit_application 'com.elgato.WaveLink*'\nremove_pkg_files 'com.elgato.WaveLink3VirtualAudio'\nforget_pkg 'com.elgato.WaveLink3VirtualAudio'\nsudo rm -rf '/Applications/Elgato Wave Link.app'\nsudo rm -rf '~/Library/LaunchAgents/com.elgato.WaveLink.plist'\nsudo rm -rf \"$APPDIR/Elgato Wave Link.app\"\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/com.elgato.WaveLink*'\ntrash $LOGGED_IN_USER '~/Library/Containers/com.elgato.WaveLink*'\ntrash $LOGGED_IN_USER '~/Library/Group Containers/Y93VXCB8Q5.group.com.corsair.elgato'\ntrash $LOGGED_IN_USER '~/Library/Logs/ElgatoWaveLink'\ntrash $LOGGED_IN_USER '~/Library/Logs/WaveLink'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.elgato.WaveLink.plist'\n",
"246335ee": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nTMPDIR=$(dirname \"$(realpath \"$INSTALLER_PATH\")\")\n# functions\n\nquit_and_track_application() {\n local bundle_id=\"$1\"\n local var_name=\"APP_WAS_RUNNING_$(echo \"$bundle_id\" | tr '.-' '__')\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e \"application id \\\"$bundle_id\\\" is running\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n eval \"export $var_name=0\"\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n eval \"export $var_name=0\"\n return\n fi\n\n # App was running, mark it for relaunch\n eval \"export $var_name=1\"\n echo \"Application '$bundle_id' was running; will relaunch after installation.\"\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" >/dev/null 2>&1; then\n if ! pgrep -f \"$bundle_id\" >/dev/null 2>&1; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n fi\n sleep 1\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n fi\n}\n\n\nrelaunch_application() {\n local bundle_id=\"$1\"\n local var_name=\"APP_WAS_RUNNING_$(echo \"$bundle_id\" | tr '.-' '__')\"\n local was_running\n\n # Check if the app was running before installation\n eval \"was_running=\\$$var_name\"\n if [[ \"$was_running\" != \"1\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping relaunching application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Relaunching application '$bundle_id'...\"\n\n # Launch the app in the logged-in user's GUI session. Apps launched by root\n # won't register with the user's Dock/GUI, so run 'open' as the console user.\n # Use 'launchctl asuser' to bootstrap into the console user's Mach namespace\n # and GUI session — 'sudo -u' alone doesn't do this, which can cause\n # LSOpenURLsWithRole() failures even when 'open' exits 0.\n local open_status=0\n if [[ $EUID -eq 0 ]]; then\n local console_uid\n console_uid=$(id -u \"$console_user\")\n /bin/launchctl asuser \"$console_uid\" sudo -u \"$console_user\" open -b \"$bundle_id\" >/dev/null 2>&1 || open_status=$?\n else\n open -b \"$bundle_id\" >/dev/null 2>&1 || open_status=$?\n fi\n\n if [[ $open_status -eq 0 ]]; then\n echo \"Application '$bundle_id' relaunched successfully.\"\n else\n echo \"Failed to relaunch application '$bundle_id'.\"\n fi\n}\n\n\n# extract contents\nMOUNT_POINT=$(mktemp -d /tmp/dmg_mount_XXXXXX)\nyes | hdiutil attach -plist -nobrowse -readonly -mountpoint \"$MOUNT_POINT\" \"$INSTALLER_PATH\" || exit 1\nsudo cp -R \"$MOUNT_POINT\"/* \"$TMPDIR\"\nhdiutil detach \"$MOUNT_POINT\" || true\n# copy to the applications folder\nquit_and_track_application 'com.elgato.WaveLink'\nif [ -d \"$APPDIR/Elgato Wave Link.app\" ]; then\n\tsudo mv \"$APPDIR/Elgato Wave Link.app\" \"$TMPDIR/Elgato Wave Link.app.bkp\"\nfi\nsudo cp -R \"$TMPDIR/Elgato Wave Link.app\" \"$APPDIR\"\nrelaunch_application 'com.elgato.WaveLink'\n"
"246335ee": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nTMPDIR=$(dirname \"$(realpath \"$INSTALLER_PATH\")\")\n# functions\n\nquit_and_track_application() {\n local bundle_id=\"$1\"\n local var_name=\"APP_WAS_RUNNING_$(echo \"$bundle_id\" | tr '.-' '__')\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e \"application id \\\"$bundle_id\\\" is running\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n eval \"export $var_name=0\"\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n eval \"export $var_name=0\"\n return\n fi\n\n # App was running, mark it for relaunch\n eval \"export $var_name=1\"\n echo \"Application '$bundle_id' was running; will relaunch after installation.\"\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" >/dev/null 2>&1; then\n if ! pgrep -f \"$bundle_id\" >/dev/null 2>&1; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n fi\n sleep 1\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n fi\n}\n\n\nrelaunch_application() {\n local bundle_id=\"$1\"\n local var_name=\"APP_WAS_RUNNING_$(echo \"$bundle_id\" | tr '.-' '__')\"\n local was_running\n\n # Check if the app was running before installation\n eval \"was_running=\\$$var_name\"\n if [[ \"$was_running\" != \"1\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping relaunching application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Relaunching application '$bundle_id'...\"\n\n # Launch the app in the logged-in user's GUI session. Apps launched by root\n # won't register with the user's Dock/GUI, so run 'open' as the console user.\n # Use 'launchctl asuser' to bootstrap into the console user's Mach namespace\n # and GUI session — 'sudo -u' alone doesn't do this, which can cause\n # LSOpenURLsWithRole() failures even when 'open' exits 0.\n local open_status=0\n if [[ $EUID -eq 0 ]]; then\n local console_uid\n console_uid=$(id -u \"$console_user\")\n /bin/launchctl asuser \"$console_uid\" sudo -u \"$console_user\" open -b \"$bundle_id\" >/dev/null 2>&1 || open_status=$?\n else\n open -b \"$bundle_id\" >/dev/null 2>&1 || open_status=$?\n fi\n\n if [[ $open_status -eq 0 ]]; then\n echo \"Application '$bundle_id' relaunched successfully.\"\n else\n echo \"Failed to relaunch application '$bundle_id'.\"\n fi\n}\n\n\n# extract contents\nMOUNT_POINT=$(mktemp -d /tmp/dmg_mount_XXXXXX)\nyes | hdiutil attach -plist -nobrowse -readonly -mountpoint \"$MOUNT_POINT\" \"$INSTALLER_PATH\" || exit 1\nsudo cp -R \"$MOUNT_POINT\"/* \"$TMPDIR\"\nhdiutil detach \"$MOUNT_POINT\" || true\n# copy to the applications folder\nquit_and_track_application 'com.elgato.WaveLink'\nif [ -d \"$APPDIR/Elgato Wave Link.app\" ]; then\n\tsudo mv \"$APPDIR/Elgato Wave Link.app\" \"$TMPDIR/Elgato Wave Link.app.bkp\"\nfi\nsudo cp -R \"$TMPDIR/Elgato Wave Link.app\" \"$APPDIR\"\nrelaunch_application 'com.elgato.WaveLink'\n",
"adcd3954": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\nexpand_pkgid_and_map() {\n local PKGID=\"$1\"\n local FUNC=\"$2\"\n if [[ \"$PKGID\" == *\"*\" ]]; then\n local prefix=\"${PKGID%\\*}\"\n echo \"Expanding wildcard for PKGID: $PKGID\"\n for receipt in $(pkgutil --pkgs | grep \"^${prefix}\"); do\n echo \"Processing $receipt\"\n \"$FUNC\" \"$receipt\"\n done\n else\n \"$FUNC\" \"$PKGID\"\n fi\n}\n\nforget_pkg() {\n local PKGID=\"$1\"\n expand_pkgid_and_map \"$PKGID\" forget_receipt\n}\n\nforget_receipt() {\n local PKGID=\"$1\"\n sudo pkgutil --forget \"$PKGID\"\n}\n\nquit_application() {\n local bundle_id=\"$1\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e \"application id \\\"$bundle_id\\\" is running\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" >/dev/null 2>&1; then\n if ! pgrep -f \"$bundle_id\" >/dev/null 2>&1; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n fi\n sleep 1\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n fi\n}\n\n\nremove_launchctl_service() {\n local service=\"$1\"\n local booleans=(\"true\" \"false\")\n local plist_status\n local paths\n local should_sudo\n\n echo \"Removing launchctl service ${service}\"\n\n # A wildcard label can't be used with launchctl or as a plist name, so expand\n # it to the labels of currently loaded services that match the pattern.\n local services=(\"$service\")\n if [[ \"$service\" == *\"*\"* ]]; then\n local regex\n # Escape regex metacharacters, turn '*' into '.*', and anchor the pattern so\n # it matches a full label rather than a substring.\n regex=$(printf '%s' \"$service\" | sed -e 's/[][(){}.^$+?|\\\\]/\\\\&/g' -e 's/\\*/.*/g')\n regex=\"^${regex}$\"\n services=()\n local id\n # Match every loaded job by label regardless of PID; launchctl list reports\n # loaded-but-not-running jobs with a \"-\" in the PID column.\n while read -r _ _ id; do\n [[ \"$id\" =~ $regex ]] && services+=(\"$id\")\n done < <(launchctl list 2>/dev/null | tail -n +2)\n if [[ ${#services[@]} -eq 0 ]]; then\n echo \"No loaded launchctl service matches ${service}\"\n return\n fi\n fi\n\n local service_label\n for service_label in \"${services[@]}\"; do\n for should_sudo in \"${booleans[@]}\"; do\n plist_status=$(launchctl list \"${service_label}\" 2>/dev/null)\n\n if [[ $plist_status == \\{* ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo launchctl remove \"${service_label}\"\n else\n launchctl remove \"${service_label}\"\n fi\n sleep 1\n fi\n\n paths=(\n \"/Library/LaunchAgents/${service_label}.plist\"\n \"/Library/LaunchDaemons/${service_label}.plist\"\n )\n\n # if not using sudo, prepend the home directory to the paths\n if [[ $should_sudo == \"false\" ]]; then\n for i in \"${!paths[@]}\"; do\n paths[i]=\"${HOME}${paths[i]}\"\n done\n fi\n\n for path in \"${paths[@]}\"; do\n if [[ -e \"$path\" ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo rm -f -- \"$path\"\n else\n rm -f -- \"$path\"\n fi\n fi\n done\n done\n done\n}\n\nremove_pkg_files() {\n local PKGID=\"$1\"\n expand_pkgid_and_map \"$PKGID\" remove_receipt_files\n}\n\nremove_receipt_files() {\n local PKGID=\"$1\"\n local PKGINFO VOLUME INSTALL_LOCATION FULL_INSTALL_LOCATION\n\n echo \"pkgutil --pkg-info-plist \\\"$PKGID\\\"\"\n PKGINFO=$(pkgutil --pkg-info-plist \"$PKGID\")\n VOLUME=$(echo \"$PKGINFO\" | awk '/<key>volume<\\/key>/ {getline; gsub(/.*<string>|<\\/string>.*/, \"\"); print}')\n INSTALL_LOCATION=$(echo \"$PKGINFO\" | awk '/<key>install-location<\\/key>/ {getline; gsub(/.*<string>|<\\/string>.*/, \"\"); print}')\n\n if [ -z \"$INSTALL_LOCATION\" ] || [ \"$INSTALL_LOCATION\" = \"/\" ]; then\n FULL_INSTALL_LOCATION=\"$VOLUME\"\n else\n FULL_INSTALL_LOCATION=\"$VOLUME/$INSTALL_LOCATION\"\n FULL_INSTALL_LOCATION=$(echo \"$FULL_INSTALL_LOCATION\" | sed 's|//|/|g')\n fi\n\n echo \"sudo pkgutil --only-files --files \\\"$PKGID\\\" | sed \\\"s|^|${FULL_INSTALL_LOCATION}/|\\\" | tr '\\\\\\\\n' '\\\\\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\"\n sudo pkgutil --only-files --files \"$PKGID\" | sed \"s|^|/${INSTALL_LOCATION}/|\" | tr '\\n' '\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\n\n echo \"sudo pkgutil --only-dirs --files \\\"$PKGID\\\" | sed \\\"s|^|${FULL_INSTALL_LOCATION}/|\\\" | grep '\\\\.app$' | tr '\\\\\\\\n' '\\\\\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\"\n sudo pkgutil --only-dirs --files \"$PKGID\" | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" | grep '\\.app$' | tr '\\n' '\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\n\n root_app_dir=$(\n sudo pkgutil --only-dirs --files \"$PKGID\" \\\n | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" \\\n | grep 'Applications' \\\n | awk '{ print length, $0 }' \\\n | sort -n \\\n | head -n1 \\\n | cut -d' ' -f2-\n )\n if [ -n \"$root_app_dir\" ]; then\n echo \"sudo rmdir -p \\\"$root_app_dir\\\" 2>/dev/null || :\"\n sudo rmdir -p \"$root_app_dir\" 2>/dev/null || :\n fi\n}\n\ntrash() {\n local logged_in_user=\"$1\"\n local target_file=\"$2\"\n local timestamp=\"$(date +%Y-%m-%d-%s)\"\n local rand=\"$(jot -r 1 0 99999)\"\n\n # replace ~ with /Users/$logged_in_user\n if [[ \"$target_file\" == ~* ]]; then\n target_file=\"/Users/$logged_in_user${target_file:1}\"\n fi\n\n local trash=\"/Users/$logged_in_user/.Trash\"\n\n # If the target contains glob characters, expand it and move each match.\n if [[ \"$target_file\" == *[*?[]* ]]; then\n local file file_name\n local matched=false\n local i=0\n # compgen -G expands the (quoted) pattern itself, so paths containing\n # spaces glob correctly; reading line by line keeps each match intact.\n while IFS= read -r file; do\n [[ -n \"$file\" ]] || continue\n [[ -e \"$file\" || -L \"$file\" ]] || continue\n matched=true\n i=$((i + 1))\n file_name=\"$(basename \"$file\")\"\n echo \"removing $file.\"\n # The per-match counter keeps matches that share a basename from\n # overwriting each other in the trash.\n mv -f \"$file\" \"$trash/${file_name}_${timestamp}_${rand}_${i}\"\n done < <(compgen -G \"$target_file\" 2>/dev/null)\n if [[ \"$matched\" == false ]]; then\n echo \"$target_file doesn't exist.\"\n fi\n return\n fi\n\n local file_name=\"$(basename \"${target_file}\")\"\n\n if [[ -e \"$target_file\" ]]; then\n echo \"removing $target_file.\"\n mv -f \"$target_file\" \"$trash/${file_name}_${timestamp}_${rand}\"\n else\n echo \"$target_file doesn't exist.\"\n fi\n}\n\nremove_launchctl_service 'com.elgato.WaveLink'\nquit_application 'com.elgato.WaveLink*'\nremove_pkg_files 'com.elgato.WaveLink3VirtualAudio'\nforget_pkg 'com.elgato.WaveLink3VirtualAudio'\nsudo rm -rf '/Applications/Elgato Wave Link.app'\nsudo rm -rf '~/Library/LaunchAgents/com.elgato.WaveLink.plist'\nsudo rm -rf \"$APPDIR/Elgato Wave Link.app\"\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/com.elgato.WaveLink*'\ntrash $LOGGED_IN_USER '~/Library/Containers/com.elgato.WaveLink*'\ntrash $LOGGED_IN_USER '~/Library/Group Containers/Y93VXCB8Q5.group.com.corsair.elgato'\ntrash $LOGGED_IN_USER '~/Library/Logs/ElgatoWaveLink'\ntrash $LOGGED_IN_USER '~/Library/Logs/WaveLink'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.elgato.WaveLink.plist'\n"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify no generated uninstall scripts pass wildcard bundle IDs to quit_application.
fd 'darwin\.json$' ee/maintained-apps/outputs -x sh -c \
  'rg -n "quit_application '\''[^'\'']*\*" "$1" || true' sh {}

Repository: fleetdm/fleet

Length of output: 24060


🌐 Web query:

AppleScript osascript application id wildcard bundle identifier support "tell application id" wildcard

💡 Result:

AppleScript does not support the use of wildcards within the string provided to 'tell application id' [1][2]. The 'application id' specifier requires a precise, fully qualified bundle identifier (or a four-character creator code) to identify the target application [3][4]. Because the identifier must be a static literal string at compile time to resolve the application's terminology, pattern matching or partial bundle identifiers are not supported [5]. If you need to dynamically target an application based on a partial match or a variable identifier, you must use alternative strategies: 1. Dynamic Targeting with 'using terms from': If you have a variable containing the full bundle ID, you can use a 'using terms from' block to provide the necessary dictionary context so the script can compile [5]. However, the identifier string itself must still be the correct, complete ID [5]. 2. Finding Applications Programmatically: If you need to find an application whose bundle ID is unknown or variable, you can use shell commands (like 'mdfind' or 'mdfind kMDItemContentType == "com.apple.application-bundle"') within your AppleScript to locate the path to the application first [6]. Once you have the path, you can use the 'application' specifier with a POSIX path or use 'System Events' to interact with the application process [5][6]. Note: The 'typeWildCard' constant exists in the underlying Apple Event Manager for handling incoming Apple events (e.g., in a handler for a scriptable app), but it is not a feature of the AppleScript 'tell application' syntax for targeting outbound events [7][8][9].

Citations:


Use an exact bundle ID for quit_application. osascript does not support wildcards in tell application id, so com.elgato.WaveLink* is treated literally and the app can stay running during uninstall.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ee/maintained-apps/outputs/elgato-wave-link/darwin.json` at line 20, The
uninstall script is calling quit_application with a wildcard bundle ID, but
osascript only accepts an exact application ID. Update the quit_application
invocation to pass the real bundle identifier used by Elgato Wave Link, and keep
the matching logic in quit_application limited to exact IDs so the app is
reliably quit before removal.

"refs": {
"9dbacbdd": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nTMPDIR=$(dirname \"$(realpath \"$INSTALLER_PATH\")\")\n# functions\n\nquit_and_track_application() {\n local bundle_id=\"$1\"\n local var_name=\"APP_WAS_RUNNING_$(echo \"$bundle_id\" | tr '.-' '__')\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e \"application id \\\"$bundle_id\\\" is running\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n eval \"export $var_name=0\"\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n eval \"export $var_name=0\"\n return\n fi\n\n # App was running, mark it for relaunch\n eval \"export $var_name=1\"\n echo \"Application '$bundle_id' was running; will relaunch after installation.\"\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" >/dev/null 2>&1; then\n if ! pgrep -f \"$bundle_id\" >/dev/null 2>&1; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n fi\n sleep 1\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n fi\n}\n\n\nrelaunch_application() {\n local bundle_id=\"$1\"\n local var_name=\"APP_WAS_RUNNING_$(echo \"$bundle_id\" | tr '.-' '__')\"\n local was_running\n\n # Check if the app was running before installation\n eval \"was_running=\\$$var_name\"\n if [[ \"$was_running\" != \"1\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping relaunching application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Relaunching application '$bundle_id'...\"\n\n # Launch the app in the logged-in user's GUI session. Apps launched by root\n # won't register with the user's Dock/GUI, so run 'open' as the console user.\n # Use 'launchctl asuser' to bootstrap into the console user's Mach namespace\n # and GUI session — 'sudo -u' alone doesn't do this, which can cause\n # LSOpenURLsWithRole() failures even when 'open' exits 0.\n local open_status=0\n if [[ $EUID -eq 0 ]]; then\n local console_uid\n console_uid=$(id -u \"$console_user\")\n /bin/launchctl asuser \"$console_uid\" sudo -u \"$console_user\" open -b \"$bundle_id\" >/dev/null 2>&1 || open_status=$?\n else\n open -b \"$bundle_id\" >/dev/null 2>&1 || open_status=$?\n fi\n\n if [[ $open_status -eq 0 ]]; then\n echo \"Application '$bundle_id' relaunched successfully.\"\n else\n echo \"Failed to relaunch application '$bundle_id'.\"\n fi\n}\n\n\n# extract contents\nMOUNT_POINT=$(mktemp -d /tmp/dmg_mount_XXXXXX)\nyes | hdiutil attach -plist -nobrowse -readonly -mountpoint \"$MOUNT_POINT\" \"$INSTALLER_PATH\" || exit 1\nsudo cp -R \"$MOUNT_POINT\"/* \"$TMPDIR\"\nhdiutil detach \"$MOUNT_POINT\" || true\n# copy to the applications folder\nquit_and_track_application 'com.jetbrains.toolbox'\nif [ -d \"$APPDIR/JetBrains Toolbox.app\" ]; then\n\tsudo mv \"$APPDIR/JetBrains Toolbox.app\" \"$TMPDIR/JetBrains Toolbox.app.bkp\"\nfi\nsudo cp -R \"$TMPDIR/JetBrains Toolbox.app\" \"$APPDIR\"\nrelaunch_application 'com.jetbrains.toolbox'\n",
"cbb68f2f": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\nremove_launchctl_service() {\n local service=\"$1\"\n local booleans=(\"true\" \"false\")\n local plist_status\n local paths\n local should_sudo\n\n echo \"Removing launchctl service ${service}\"\n\n for should_sudo in \"${booleans[@]}\"; do\n plist_status=$(launchctl list \"${service}\" 2>/dev/null)\n\n if [[ $plist_status == \\{* ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo launchctl remove \"${service}\"\n else\n launchctl remove \"${service}\"\n fi\n sleep 1\n fi\n\n paths=(\n \"/Library/LaunchAgents/${service}.plist\"\n \"/Library/LaunchDaemons/${service}.plist\"\n )\n\n # if not using sudo, prepend the home directory to the paths\n if [[ $should_sudo == \"false\" ]]; then\n for i in \"${!paths[@]}\"; do\n paths[i]=\"${HOME}${paths[i]}\"\n done\n fi\n\n for path in \"${paths[@]}\"; do\n if [[ -e \"$path\" ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo rm -f -- \"$path\"\n else\n rm -f -- \"$path\"\n fi\n fi\n done\n done\n}\n\nsend_signal() {\n local signal=\"$1\"\n local bundle_id=\"$2\"\n local logged_in_user=\"$3\"\n local logged_in_uid pids\n\n if [ -z \"$signal\" ] || [ -z \"$bundle_id\" ] || [ -z \"$logged_in_user\" ]; then\n echo \"Usage: uninstall_signal <signal> <bundle_id> <logged_in_user>\"\n return 1\n fi\n\n logged_in_uid=$(id -u \"$logged_in_user\")\n if [ -z \"$logged_in_uid\" ]; then\n echo \"Could not find UID for user '$logged_in_user'.\"\n return 1\n fi\n\n echo \"Signalling '$signal' to application ID '$bundle_id' for user '$logged_in_user'\"\n\n pids=$(/bin/launchctl asuser \"$logged_in_uid\" sudo -iu \"$logged_in_user\" /bin/launchctl list | awk -v bundle_id=\"$bundle_id\" '\n $3 ~ bundle_id { print $1 }')\n\n if [ -z \"$pids\" ]; then\n echo \"No processes found for bundle ID '$bundle_id'.\"\n return 0\n fi\n\n echo \"Unix PIDs are $pids for processes with bundle identifier $bundle_id\"\n for pid in $pids; do\n if kill -s \"$signal\" \"$pid\" 2>/dev/null; then\n echo \"Successfully signaled PID $pid with signal $signal.\"\n else\n echo \"Failed to kill PID $pid with signal $signal. Check permissions.\"\n fi\n done\n\n sleep 3\n}\n\ntrash() {\n local logged_in_user=\"$1\"\n local target_file=\"$2\"\n local timestamp=\"$(date +%Y-%m-%d-%s)\"\n local rand=\"$(jot -r 1 0 99999)\"\n\n # replace ~ with /Users/$logged_in_user\n if [[ \"$target_file\" == ~* ]]; then\n target_file=\"/Users/$logged_in_user${target_file:1}\"\n fi\n\n local trash=\"/Users/$logged_in_user/.Trash\"\n\n # If the target contains glob characters, expand it and move each match.\n if [[ \"$target_file\" == *[*?[]* ]]; then\n local file file_name\n local matched=false\n local i=0\n # compgen -G expands the (quoted) pattern itself, so paths containing\n # spaces glob correctly; reading line by line keeps each match intact.\n while IFS= read -r file; do\n [[ -n \"$file\" ]] || continue\n [[ -e \"$file\" || -L \"$file\" ]] || continue\n matched=true\n i=$((i + 1))\n file_name=\"$(basename \"$file\")\"\n echo \"removing $file.\"\n # The per-match counter keeps matches that share a basename from\n # overwriting each other in the trash.\n mv -f \"$file\" \"$trash/${file_name}_${timestamp}_${rand}_${i}\"\n done < <(compgen -G \"$target_file\" 2>/dev/null)\n if [[ \"$matched\" == false ]]; then\n echo \"$target_file doesn't exist.\"\n fi\n return\n fi\n\n local file_name=\"$(basename \"${target_file}\")\"\n\n if [[ -e \"$target_file\" ]]; then\n echo \"removing $target_file.\"\n mv -f \"$target_file\" \"$trash/${file_name}_${timestamp}_${rand}\"\n else\n echo \"$target_file doesn't exist.\"\n fi\n}\n\nremove_launchctl_service 'com.jetbrains.toolbox'\nsend_signal 'TERM' 'com.jetbrains.toolbox' \"$LOGGED_IN_USER\"\nsudo rm -rf \"$APPDIR/JetBrains Toolbox.app\"\nsudo rmdir '~/Library/Application Support/JetBrains'\nsudo rmdir '~/Library/Caches/JetBrains'\nsudo rmdir '~/Library/Logs/JetBrains'\ntrash $LOGGED_IN_USER '~/Library/Application Support/JetBrains/Toolbox'\ntrash $LOGGED_IN_USER '~/Library/Caches/JetBrains/Toolbox'\ntrash $LOGGED_IN_USER '~/Library/Logs/JetBrains/Toolbox'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.jetbrains.toolbox.renderer.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/com.jetbrains.toolbox.savedState'\n"
"276d8363": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\nremove_launchctl_service() {\n local service=\"$1\"\n local booleans=(\"true\" \"false\")\n local plist_status\n local paths\n local should_sudo\n\n echo \"Removing launchctl service ${service}\"\n\n # A wildcard label can't be used with launchctl or as a plist name, so expand\n # it to the labels of currently loaded services that match the pattern.\n local services=(\"$service\")\n if [[ \"$service\" == *\"*\"* ]]; then\n local regex\n # Escape regex metacharacters, turn '*' into '.*', and anchor the pattern so\n # it matches a full label rather than a substring.\n regex=$(printf '%s' \"$service\" | sed -e 's/[][(){}.^$+?|\\\\]/\\\\&/g' -e 's/\\*/.*/g')\n regex=\"^${regex}$\"\n services=()\n local id\n # Match every loaded job by label regardless of PID; launchctl list reports\n # loaded-but-not-running jobs with a \"-\" in the PID column.\n while read -r _ _ id; do\n [[ \"$id\" =~ $regex ]] && services+=(\"$id\")\n done < <(launchctl list 2>/dev/null | tail -n +2)\n if [[ ${#services[@]} -eq 0 ]]; then\n echo \"No loaded launchctl service matches ${service}\"\n return\n fi\n fi\n\n local service_label\n for service_label in \"${services[@]}\"; do\n for should_sudo in \"${booleans[@]}\"; do\n plist_status=$(launchctl list \"${service_label}\" 2>/dev/null)\n\n if [[ $plist_status == \\{* ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo launchctl remove \"${service_label}\"\n else\n launchctl remove \"${service_label}\"\n fi\n sleep 1\n fi\n\n paths=(\n \"/Library/LaunchAgents/${service_label}.plist\"\n \"/Library/LaunchDaemons/${service_label}.plist\"\n )\n\n # if not using sudo, prepend the home directory to the paths\n if [[ $should_sudo == \"false\" ]]; then\n for i in \"${!paths[@]}\"; do\n paths[i]=\"${HOME}${paths[i]}\"\n done\n fi\n\n for path in \"${paths[@]}\"; do\n if [[ -e \"$path\" ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo rm -f -- \"$path\"\n else\n rm -f -- \"$path\"\n fi\n fi\n done\n done\n done\n}\n\nsend_signal() {\n local signal=\"$1\"\n local bundle_id=\"$2\"\n local logged_in_user=\"$3\"\n local logged_in_uid pids\n\n if [ -z \"$signal\" ] || [ -z \"$bundle_id\" ] || [ -z \"$logged_in_user\" ]; then\n echo \"Usage: uninstall_signal <signal> <bundle_id> <logged_in_user>\"\n return 1\n fi\n\n logged_in_uid=$(id -u \"$logged_in_user\")\n if [ -z \"$logged_in_uid\" ]; then\n echo \"Could not find UID for user '$logged_in_user'.\"\n return 1\n fi\n\n echo \"Signalling '$signal' to application ID '$bundle_id' for user '$logged_in_user'\"\n\n pids=$(/bin/launchctl asuser \"$logged_in_uid\" sudo -iu \"$logged_in_user\" /bin/launchctl list | awk -v bundle_id=\"$bundle_id\" '\n $3 ~ bundle_id { print $1 }')\n\n if [ -z \"$pids\" ]; then\n echo \"No processes found for bundle ID '$bundle_id'.\"\n return 0\n fi\n\n echo \"Unix PIDs are $pids for processes with bundle identifier $bundle_id\"\n for pid in $pids; do\n if kill -s \"$signal\" \"$pid\" 2>/dev/null; then\n echo \"Successfully signaled PID $pid with signal $signal.\"\n else\n echo \"Failed to kill PID $pid with signal $signal. Check permissions.\"\n fi\n done\n\n sleep 3\n}\n\ntrash() {\n local logged_in_user=\"$1\"\n local target_file=\"$2\"\n local timestamp=\"$(date +%Y-%m-%d-%s)\"\n local rand=\"$(jot -r 1 0 99999)\"\n\n # replace ~ with /Users/$logged_in_user\n if [[ \"$target_file\" == ~* ]]; then\n target_file=\"/Users/$logged_in_user${target_file:1}\"\n fi\n\n local trash=\"/Users/$logged_in_user/.Trash\"\n\n # If the target contains glob characters, expand it and move each match.\n if [[ \"$target_file\" == *[*?[]* ]]; then\n local file file_name\n local matched=false\n local i=0\n # compgen -G expands the (quoted) pattern itself, so paths containing\n # spaces glob correctly; reading line by line keeps each match intact.\n while IFS= read -r file; do\n [[ -n \"$file\" ]] || continue\n [[ -e \"$file\" || -L \"$file\" ]] || continue\n matched=true\n i=$((i + 1))\n file_name=\"$(basename \"$file\")\"\n echo \"removing $file.\"\n # The per-match counter keeps matches that share a basename from\n # overwriting each other in the trash.\n mv -f \"$file\" \"$trash/${file_name}_${timestamp}_${rand}_${i}\"\n done < <(compgen -G \"$target_file\" 2>/dev/null)\n if [[ \"$matched\" == false ]]; then\n echo \"$target_file doesn't exist.\"\n fi\n return\n fi\n\n local file_name=\"$(basename \"${target_file}\")\"\n\n if [[ -e \"$target_file\" ]]; then\n echo \"removing $target_file.\"\n mv -f \"$target_file\" \"$trash/${file_name}_${timestamp}_${rand}\"\n else\n echo \"$target_file doesn't exist.\"\n fi\n}\n\nremove_launchctl_service 'com.jetbrains.toolbox'\nsend_signal 'TERM' 'com.jetbrains.toolbox' \"$LOGGED_IN_USER\"\nsudo rm -rf \"$APPDIR/JetBrains Toolbox.app\"\nsudo rmdir '~/Library/Application Support/JetBrains'\nsudo rmdir '~/Library/Caches/JetBrains'\nsudo rmdir '~/Library/Logs/JetBrains'\ntrash $LOGGED_IN_USER '~/Library/Application Support/JetBrains/Toolbox'\ntrash $LOGGED_IN_USER '~/Library/Caches/JetBrains/Toolbox'\ntrash $LOGGED_IN_USER '~/Library/Logs/JetBrains/Toolbox'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.jetbrains.toolbox.renderer.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/com.jetbrains.toolbox.savedState'\n",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Keep wildcard plist cleanup even when no job is loaded.

The wildcard branch populates services only from launchctl list, then returns when no loaded label matches. That skips matching LaunchAgent/LaunchDaemon plist removal for unloaded jobs, leaving stale launchd items that can come back later. Keep the original wildcard pattern as a plist-cleanup candidate, or discover matching plist basenames before returning. Since these outputs are generated, fix the maintained-apps template/generator and regenerate.

Suggested helper shape
   local services=("$service")
   if [[ "$service" == *"*"* ]]; then
+    local wildcard_service="$service"
     local regex
     regex=$(printf '%s' "$service" | sed -e 's/[][(){}.^$+?|\\]/\\&/g' -e 's/\*/.*/g')
     regex="^${regex}$"
     services=()
@@
-    if [[ ${`#services`[@]} -eq 0 ]]; then
-      echo "No loaded launchctl service matches ${service}"
-      return
-    fi
+    # Also keep the wildcard pattern so matching plist files are removed even
+    # when the corresponding job is currently unloaded.
+    services+=("$wildcard_service")
   fi
@@
       for path in "${paths[@]}"; do
+        if [[ "$path" == *"*"* ]]; then
+          while IFS= read -r matched_path; do
+            if [[ $should_sudo == "true" ]]; then
+              sudo rm -f -- "$matched_path"
+            else
+              rm -f -- "$matched_path"
+            fi
+          done < <(compgen -G "$path" 2>/dev/null)
+          continue
+        fi
         if [[ -e "$path" ]]; then
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"276d8363": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\nremove_launchctl_service() {\n local service=\"$1\"\n local booleans=(\"true\" \"false\")\n local plist_status\n local paths\n local should_sudo\n\n echo \"Removing launchctl service ${service}\"\n\n # A wildcard label can't be used with launchctl or as a plist name, so expand\n # it to the labels of currently loaded services that match the pattern.\n local services=(\"$service\")\n if [[ \"$service\" == *\"*\"* ]]; then\n local regex\n # Escape regex metacharacters, turn '*' into '.*', and anchor the pattern so\n # it matches a full label rather than a substring.\n regex=$(printf '%s' \"$service\" | sed -e 's/[][(){}.^$+?|\\\\]/\\\\&/g' -e 's/\\*/.*/g')\n regex=\"^${regex}$\"\n services=()\n local id\n # Match every loaded job by label regardless of PID; launchctl list reports\n # loaded-but-not-running jobs with a \"-\" in the PID column.\n while read -r _ _ id; do\n [[ \"$id\" =~ $regex ]] && services+=(\"$id\")\n done < <(launchctl list 2>/dev/null | tail -n +2)\n if [[ ${#services[@]} -eq 0 ]]; then\n echo \"No loaded launchctl service matches ${service}\"\n return\n fi\n fi\n\n local service_label\n for service_label in \"${services[@]}\"; do\n for should_sudo in \"${booleans[@]}\"; do\n plist_status=$(launchctl list \"${service_label}\" 2>/dev/null)\n\n if [[ $plist_status == \\{* ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo launchctl remove \"${service_label}\"\n else\n launchctl remove \"${service_label}\"\n fi\n sleep 1\n fi\n\n paths=(\n \"/Library/LaunchAgents/${service_label}.plist\"\n \"/Library/LaunchDaemons/${service_label}.plist\"\n )\n\n # if not using sudo, prepend the home directory to the paths\n if [[ $should_sudo == \"false\" ]]; then\n for i in \"${!paths[@]}\"; do\n paths[i]=\"${HOME}${paths[i]}\"\n done\n fi\n\n for path in \"${paths[@]}\"; do\n if [[ -e \"$path\" ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo rm -f -- \"$path\"\n else\n rm -f -- \"$path\"\n fi\n fi\n done\n done\n done\n}\n\nsend_signal() {\n local signal=\"$1\"\n local bundle_id=\"$2\"\n local logged_in_user=\"$3\"\n local logged_in_uid pids\n\n if [ -z \"$signal\" ] || [ -z \"$bundle_id\" ] || [ -z \"$logged_in_user\" ]; then\n echo \"Usage: uninstall_signal <signal> <bundle_id> <logged_in_user>\"\n return 1\n fi\n\n logged_in_uid=$(id -u \"$logged_in_user\")\n if [ -z \"$logged_in_uid\" ]; then\n echo \"Could not find UID for user '$logged_in_user'.\"\n return 1\n fi\n\n echo \"Signalling '$signal' to application ID '$bundle_id' for user '$logged_in_user'\"\n\n pids=$(/bin/launchctl asuser \"$logged_in_uid\" sudo -iu \"$logged_in_user\" /bin/launchctl list | awk -v bundle_id=\"$bundle_id\" '\n $3 ~ bundle_id { print $1 }')\n\n if [ -z \"$pids\" ]; then\n echo \"No processes found for bundle ID '$bundle_id'.\"\n return 0\n fi\n\n echo \"Unix PIDs are $pids for processes with bundle identifier $bundle_id\"\n for pid in $pids; do\n if kill -s \"$signal\" \"$pid\" 2>/dev/null; then\n echo \"Successfully signaled PID $pid with signal $signal.\"\n else\n echo \"Failed to kill PID $pid with signal $signal. Check permissions.\"\n fi\n done\n\n sleep 3\n}\n\ntrash() {\n local logged_in_user=\"$1\"\n local target_file=\"$2\"\n local timestamp=\"$(date +%Y-%m-%d-%s)\"\n local rand=\"$(jot -r 1 0 99999)\"\n\n # replace ~ with /Users/$logged_in_user\n if [[ \"$target_file\" == ~* ]]; then\n target_file=\"/Users/$logged_in_user${target_file:1}\"\n fi\n\n local trash=\"/Users/$logged_in_user/.Trash\"\n\n # If the target contains glob characters, expand it and move each match.\n if [[ \"$target_file\" == *[*?[]* ]]; then\n local file file_name\n local matched=false\n local i=0\n # compgen -G expands the (quoted) pattern itself, so paths containing\n # spaces glob correctly; reading line by line keeps each match intact.\n while IFS= read -r file; do\n [[ -n \"$file\" ]] || continue\n [[ -e \"$file\" || -L \"$file\" ]] || continue\n matched=true\n i=$((i + 1))\n file_name=\"$(basename \"$file\")\"\n echo \"removing $file.\"\n # The per-match counter keeps matches that share a basename from\n # overwriting each other in the trash.\n mv -f \"$file\" \"$trash/${file_name}_${timestamp}_${rand}_${i}\"\n done < <(compgen -G \"$target_file\" 2>/dev/null)\n if [[ \"$matched\" == false ]]; then\n echo \"$target_file doesn't exist.\"\n fi\n return\n fi\n\n local file_name=\"$(basename \"${target_file}\")\"\n\n if [[ -e \"$target_file\" ]]; then\n echo \"removing $target_file.\"\n mv -f \"$target_file\" \"$trash/${file_name}_${timestamp}_${rand}\"\n else\n echo \"$target_file doesn't exist.\"\n fi\n}\n\nremove_launchctl_service 'com.jetbrains.toolbox'\nsend_signal 'TERM' 'com.jetbrains.toolbox' \"$LOGGED_IN_USER\"\nsudo rm -rf \"$APPDIR/JetBrains Toolbox.app\"\nsudo rmdir '~/Library/Application Support/JetBrains'\nsudo rmdir '~/Library/Caches/JetBrains'\nsudo rmdir '~/Library/Logs/JetBrains'\ntrash $LOGGED_IN_USER '~/Library/Application Support/JetBrains/Toolbox'\ntrash $LOGGED_IN_USER '~/Library/Caches/JetBrains/Toolbox'\ntrash $LOGGED_IN_USER '~/Library/Logs/JetBrains/Toolbox'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.jetbrains.toolbox.renderer.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/com.jetbrains.toolbox.savedState'\n",
local services=("$service")
if [[ "$service" == *"*"* ]]; then
local wildcard_service="$service"
local regex
# Escape regex metacharacters, turn '*' into '.*', and anchor the pattern so
# it matches a full label rather than a substring.
regex=$(printf '%s' "$service" | sed -e 's/[][(){}.^$+?|\\]/\\&/g' -e 's/\*/.*/g')
regex="^${regex}$"
services=()
local id
# Match every loaded job by label regardless of PID; launchctl list reports
# loaded-but-not-running jobs with a "-" in the PID column.
while read -r _ _ id; do
[[ "$id" =~ $regex ]] && services+=("$id")
done < <(launchctl list 2>/dev/null | tail -n +2)
# Also keep the wildcard pattern so matching plist files are removed even
# when the corresponding job is currently unloaded.
services+=("$wildcard_service")
fi
local service_label
for service_label in "${services[@]}"; do
for should_sudo in "${booleans[@]}"; do
plist_status=$(launchctl list "${service_label}" 2>/dev/null)
if [[ $plist_status == \{* ]]; then
if [[ $should_sudo == "true" ]]; then
sudo launchctl remove "${service_label}"
else
launchctl remove "${service_label}"
fi
sleep 1
fi
paths=(
"/Library/LaunchAgents/${service_label}.plist"
"/Library/LaunchDaemons/${service_label}.plist"
)
# if not using sudo, prepend the home directory to the paths
if [[ $should_sudo == "false" ]]; then
for i in "${!paths[@]}"; do
paths[i]="${HOME}${paths[i]}"
done
fi
for path in "${paths[@]}"; do
if [[ "$path" == *"*"* ]]; then
while IFS= read -r matched_path; do
if [[ $should_sudo == "true" ]]; then
sudo rm -f -- "$matched_path"
else
rm -f -- "$matched_path"
fi
done < <(compgen -G "$path" 2>/dev/null)
continue
fi
if [[ -e "$path" ]]; then
if [[ $should_sudo == "true" ]]; then
sudo rm -f -- "$path"
else
rm -f -- "$path"
fi
fi
done
done
done
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ee/maintained-apps/outputs/jetbrains-toolbox/darwin.json` at line 19, The
wildcard handling in remove_launchctl_service currently returns early when
launchctl list finds no loaded matches, which prevents cleanup of matching
LaunchAgent/LaunchDaemon plist files for unloaded jobs. Update the wildcard
branch to retain the original pattern as a cleanup candidate or to enumerate
matching plist basenames before the no-match return, so the later plist removal
logic still runs. Make the fix in the maintained-apps template/generator that
produces this JetBrains Toolbox output, then regenerate the output.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Expand user-library parent paths before removing them.

sudo rmdir '~/Library/...' treats ~ literally, and these parent removals run before the child trash calls. Move them after the relevant trash calls and target /Users/$LOGGED_IN_USER/....

Example cleanup adjustment
-sudo rmdir '~/Library/Application Support/JetBrains'
-sudo rmdir '~/Library/Caches/JetBrains'
-sudo rmdir '~/Library/Logs/JetBrains'
 trash $LOGGED_IN_USER '~/Library/Application Support/JetBrains/Toolbox'
 trash $LOGGED_IN_USER '~/Library/Caches/JetBrains/Toolbox'
 trash $LOGGED_IN_USER '~/Library/Logs/JetBrains/Toolbox'
+rmdir "/Users/$LOGGED_IN_USER/Library/Application Support/JetBrains" 2>/dev/null || true
+rmdir "/Users/$LOGGED_IN_USER/Library/Caches/JetBrains" 2>/dev/null || true
+rmdir "/Users/$LOGGED_IN_USER/Library/Logs/JetBrains" 2>/dev/null || true
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"276d8363": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\nremove_launchctl_service() {\n local service=\"$1\"\n local booleans=(\"true\" \"false\")\n local plist_status\n local paths\n local should_sudo\n\n echo \"Removing launchctl service ${service}\"\n\n # A wildcard label can't be used with launchctl or as a plist name, so expand\n # it to the labels of currently loaded services that match the pattern.\n local services=(\"$service\")\n if [[ \"$service\" == *\"*\"* ]]; then\n local regex\n # Escape regex metacharacters, turn '*' into '.*', and anchor the pattern so\n # it matches a full label rather than a substring.\n regex=$(printf '%s' \"$service\" | sed -e 's/[][(){}.^$+?|\\\\]/\\\\&/g' -e 's/\\*/.*/g')\n regex=\"^${regex}$\"\n services=()\n local id\n # Match every loaded job by label regardless of PID; launchctl list reports\n # loaded-but-not-running jobs with a \"-\" in the PID column.\n while read -r _ _ id; do\n [[ \"$id\" =~ $regex ]] && services+=(\"$id\")\n done < <(launchctl list 2>/dev/null | tail -n +2)\n if [[ ${#services[@]} -eq 0 ]]; then\n echo \"No loaded launchctl service matches ${service}\"\n return\n fi\n fi\n\n local service_label\n for service_label in \"${services[@]}\"; do\n for should_sudo in \"${booleans[@]}\"; do\n plist_status=$(launchctl list \"${service_label}\" 2>/dev/null)\n\n if [[ $plist_status == \\{* ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo launchctl remove \"${service_label}\"\n else\n launchctl remove \"${service_label}\"\n fi\n sleep 1\n fi\n\n paths=(\n \"/Library/LaunchAgents/${service_label}.plist\"\n \"/Library/LaunchDaemons/${service_label}.plist\"\n )\n\n # if not using sudo, prepend the home directory to the paths\n if [[ $should_sudo == \"false\" ]]; then\n for i in \"${!paths[@]}\"; do\n paths[i]=\"${HOME}${paths[i]}\"\n done\n fi\n\n for path in \"${paths[@]}\"; do\n if [[ -e \"$path\" ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo rm -f -- \"$path\"\n else\n rm -f -- \"$path\"\n fi\n fi\n done\n done\n done\n}\n\nsend_signal() {\n local signal=\"$1\"\n local bundle_id=\"$2\"\n local logged_in_user=\"$3\"\n local logged_in_uid pids\n\n if [ -z \"$signal\" ] || [ -z \"$bundle_id\" ] || [ -z \"$logged_in_user\" ]; then\n echo \"Usage: uninstall_signal <signal> <bundle_id> <logged_in_user>\"\n return 1\n fi\n\n logged_in_uid=$(id -u \"$logged_in_user\")\n if [ -z \"$logged_in_uid\" ]; then\n echo \"Could not find UID for user '$logged_in_user'.\"\n return 1\n fi\n\n echo \"Signalling '$signal' to application ID '$bundle_id' for user '$logged_in_user'\"\n\n pids=$(/bin/launchctl asuser \"$logged_in_uid\" sudo -iu \"$logged_in_user\" /bin/launchctl list | awk -v bundle_id=\"$bundle_id\" '\n $3 ~ bundle_id { print $1 }')\n\n if [ -z \"$pids\" ]; then\n echo \"No processes found for bundle ID '$bundle_id'.\"\n return 0\n fi\n\n echo \"Unix PIDs are $pids for processes with bundle identifier $bundle_id\"\n for pid in $pids; do\n if kill -s \"$signal\" \"$pid\" 2>/dev/null; then\n echo \"Successfully signaled PID $pid with signal $signal.\"\n else\n echo \"Failed to kill PID $pid with signal $signal. Check permissions.\"\n fi\n done\n\n sleep 3\n}\n\ntrash() {\n local logged_in_user=\"$1\"\n local target_file=\"$2\"\n local timestamp=\"$(date +%Y-%m-%d-%s)\"\n local rand=\"$(jot -r 1 0 99999)\"\n\n # replace ~ with /Users/$logged_in_user\n if [[ \"$target_file\" == ~* ]]; then\n target_file=\"/Users/$logged_in_user${target_file:1}\"\n fi\n\n local trash=\"/Users/$logged_in_user/.Trash\"\n\n # If the target contains glob characters, expand it and move each match.\n if [[ \"$target_file\" == *[*?[]* ]]; then\n local file file_name\n local matched=false\n local i=0\n # compgen -G expands the (quoted) pattern itself, so paths containing\n # spaces glob correctly; reading line by line keeps each match intact.\n while IFS= read -r file; do\n [[ -n \"$file\" ]] || continue\n [[ -e \"$file\" || -L \"$file\" ]] || continue\n matched=true\n i=$((i + 1))\n file_name=\"$(basename \"$file\")\"\n echo \"removing $file.\"\n # The per-match counter keeps matches that share a basename from\n # overwriting each other in the trash.\n mv -f \"$file\" \"$trash/${file_name}_${timestamp}_${rand}_${i}\"\n done < <(compgen -G \"$target_file\" 2>/dev/null)\n if [[ \"$matched\" == false ]]; then\n echo \"$target_file doesn't exist.\"\n fi\n return\n fi\n\n local file_name=\"$(basename \"${target_file}\")\"\n\n if [[ -e \"$target_file\" ]]; then\n echo \"removing $target_file.\"\n mv -f \"$target_file\" \"$trash/${file_name}_${timestamp}_${rand}\"\n else\n echo \"$target_file doesn't exist.\"\n fi\n}\n\nremove_launchctl_service 'com.jetbrains.toolbox'\nsend_signal 'TERM' 'com.jetbrains.toolbox' \"$LOGGED_IN_USER\"\nsudo rm -rf \"$APPDIR/JetBrains Toolbox.app\"\nsudo rmdir '~/Library/Application Support/JetBrains'\nsudo rmdir '~/Library/Caches/JetBrains'\nsudo rmdir '~/Library/Logs/JetBrains'\ntrash $LOGGED_IN_USER '~/Library/Application Support/JetBrains/Toolbox'\ntrash $LOGGED_IN_USER '~/Library/Caches/JetBrains/Toolbox'\ntrash $LOGGED_IN_USER '~/Library/Logs/JetBrains/Toolbox'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.jetbrains.toolbox.renderer.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/com.jetbrains.toolbox.savedState'\n",
"276d8363": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\nremove_launchctl_service() {\n local service=\"$1\"\n local booleans=(\"true\" \"false\")\n local plist_status\n local paths\n local should_sudo\n\n echo \"Removing launchctl service ${service}\"\n\n # A wildcard label can't be used with launchctl or as a plist name, so expand\n # it to the labels of currently loaded services that match the pattern.\n local services=(\"$service\")\n if [[ \"$service\" == *\"*\"* ]]; then\n local regex\n # Escape regex metacharacters, turn '*' into '.*', and anchor the pattern so\n # it matches a full label rather than a substring.\n regex=$(printf '%s' \"$service\" | sed -e 's/[][(){}.^$+?|\\\\]/\\\\&/g' -e 's/\\*/.*/g')\n regex=\"^${regex}$\"\n services=()\n local id\n # Match every loaded job by label regardless of PID; launchctl list reports\n # loaded-but-not-running jobs with a \"-\" in the PID column.\n while read -r _ _ id; do\n [[ \"$id\" =~ $regex ]] && services+=(\"$id\")\n done < <(launchctl list 2>/dev/null | tail -n +2)\n if [[ ${`#services`[@]} -eq 0 ]]; then\n echo \"No loaded launchctl service matches ${service}\"\n return\n fi\n fi\n\n local service_label\n for service_label in \"${services[@]}\"; do\n for should_sudo in \"${booleans[@]}\"; do\n plist_status=$(launchctl list \"${service_label}\" 2>/dev/null)\n\n if [[ $plist_status == \\{* ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo launchctl remove \"${service_label}\"\n else\n launchctl remove \"${service_label}\"\n fi\n sleep 1\n fi\n\n paths=(\n \"/Library/LaunchAgents/${service_label}.plist\"\n \"/Library/LaunchDaemons/${service_label}.plist\"\n )\n\n # if not using sudo, prepend the home directory to the paths\n if [[ $should_sudo == \"false\" ]]; then\n for i in \"${!paths[@]}\"; do\n paths[i]=\"${HOME}${paths[i]}\"\n done\n fi\n\n for path in \"${paths[@]}\"; do\n if [[ -e \"$path\" ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo rm -f -- \"$path\"\n else\n rm -f -- \"$path\"\n fi\n fi\n done\n done\n done\n}\n\nsend_signal() {\n local signal=\"$1\"\n local bundle_id=\"$2\"\n local logged_in_user=\"$3\"\n local logged_in_uid pids\n\n if [ -z \"$signal\" ] || [ -z \"$bundle_id\" ] || [ -z \"$logged_in_user\" ]; then\n echo \"Usage: uninstall_signal <signal> <bundle_id> <logged_in_user>\"\n return 1\n fi\n\n logged_in_uid=$(id -u \"$logged_in_user\")\n if [ -z \"$logged_in_uid\" ]; then\n echo \"Could not find UID for user '$logged_in_user'.\"\n return 1\n fi\n\n echo \"Signalling '$signal' to application ID '$bundle_id' for user '$logged_in_user'\"\n\n pids=$(/bin/launchctl asuser \"$logged_in_uid\" sudo -iu \"$logged_in_user\" /bin/launchctl list | awk -v bundle_id=\"$bundle_id\" '\n $3 ~ bundle_id { print $1 }')\n\n if [ -z \"$pids\" ]; then\n echo \"No processes found for bundle ID '$bundle_id'.\"\n return 0\n fi\n\n echo \"Unix PIDs are $pids for processes with bundle identifier $bundle_id\"\n for pid in $pids; do\n if kill -s \"$signal\" \"$pid\" 2>/dev/null; then\n echo \"Successfully signaled PID $pid with signal $signal.\"\n else\n echo \"Failed to kill PID $pid with signal $signal. Check permissions.\"\n fi\n done\n\n sleep 3\n}\n\ntrash() {\n local logged_in_user=\"$1\"\n local target_file=\"$2\"\n local timestamp=\"$(date +%Y-%m-%d-%s)\"\n local rand=\"$(jot -r 1 0 99999)\"\n\n # replace ~ with /Users/$logged_in_user\n if [[ \"$target_file\" == ~* ]]; then\n target_file=\"/Users/$logged_in_user${target_file:1}\"\n fi\n\n local trash=\"/Users/$logged_in_user/.Trash\"\n\n # If the target contains glob characters, expand it and move each match.\n if [[ \"$target_file\" == *[*?[]* ]]; then\n local file file_name\n local matched=false\n local i=0\n # compgen -G expands the (quoted) pattern itself, so paths containing\n # spaces glob correctly; reading line by line keeps each match intact.\n while IFS= read -r file; do\n [[ -n \"$file\" ]] || continue\n [[ -e \"$file\" || -L \"$file\" ]] || continue\n matched=true\n i=$((i + 1))\n file_name=\"$(basename \"$file\")\"\n echo \"removing $file.\"\n # The per-match counter keeps matches that share a basename from\n # overwriting each other in the trash.\n mv -f \"$file\" \"$trash/${file_name}_${timestamp}_${rand}_${i}\"\n done < <(compgen -G \"$target_file\" 2>/dev/null)\n if [[ \"$matched\" == false ]]; then\n echo \"$target_file doesn't exist.\"\n fi\n return\n fi\n\n local file_name=\"$(basename \"${target_file}\")\"\n\n if [[ -e \"$target_file\" ]]; then\n echo \"removing $target_file.\"\n mv -f \"$target_file\" \"$trash/${file_name}_${timestamp}_${rand}\"\n else\n echo \"$target_file doesn't exist.\"\n fi\n}\n\nremove_launchctl_service 'com.jetbrains.toolbox'\nsend_signal 'TERM' 'com.jetbrains.toolbox' \"$LOGGED_IN_USER\"\nsudo rm -rf \"$APPDIR/JetBrains Toolbox.app\"\ntrash $LOGGED_IN_USER '~/Library/Application Support/JetBrains/Toolbox'\ntrash $LOGGED_IN_USER '~/Library/Caches/JetBrains/Toolbox'\ntrash $LOGGED_IN_USER '~/Library/Logs/JetBrains/Toolbox'\nrmdir \"/Users/$LOGGED_IN_USER/Library/Application Support/JetBrains\" 2>/dev/null || true\nrmdir \"/Users/$LOGGED_IN_USER/Library/Caches/JetBrains\" 2>/dev/null || true\nrmdir \"/Users/$LOGGED_IN_USER/Library/Logs/JetBrains\" 2>/dev/null || true\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.jetbrains.toolbox.renderer.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/com.jetbrains.toolbox.savedState'\n",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ee/maintained-apps/outputs/jetbrains-toolbox/darwin.json` at line 19, The
uninstall script is removing user-library parent directories with a literal
tilde, so the paths are not being expanded and the removals happen before the
related child items are trashed. Update the JetBrains Toolbox cleanup sequence
in darwin.json by moving the parent rmdir cleanup after the trash calls, and
change those targets to use the resolved user home path via LOGGED_IN_USER
instead of '~/...'. Refer to the uninstall flow around remove_launchctl_service,
send_signal, trash, and the subsequent sudo rmdir calls.

],
"refs": {
"24478eff": "#!/bin/bash\n\n# variables\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\nexpand_pkgid_and_map() {\n local PKGID=\"$1\"\n local FUNC=\"$2\"\n if [[ \"$PKGID\" == *\"*\" ]]; then\n local prefix=\"${PKGID%\\*}\"\n echo \"Expanding wildcard for PKGID: $PKGID\"\n for receipt in $(pkgutil --pkgs | grep \"^${prefix}\"); do\n echo \"Processing $receipt\"\n \"$FUNC\" \"$receipt\"\n done\n else\n \"$FUNC\" \"$PKGID\"\n fi\n}\n\nforget_pkg() {\n local PKGID=\"$1\"\n expand_pkgid_and_map \"$PKGID\" forget_receipt\n}\n\nforget_receipt() {\n local PKGID=\"$1\"\n sudo pkgutil --forget \"$PKGID\"\n}\n\nquit_application() {\n local bundle_id=\"$1\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e \"application id \\\"$bundle_id\\\" is running\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" >/dev/null 2>&1; then\n if ! pgrep -f \"$bundle_id\" >/dev/null 2>&1; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n fi\n sleep 1\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n fi\n}\n\n\nremove_launchctl_service() {\n local service=\"$1\"\n local booleans=(\"true\" \"false\")\n local plist_status\n local paths\n local should_sudo\n\n echo \"Removing launchctl service ${service}\"\n\n for should_sudo in \"${booleans[@]}\"; do\n plist_status=$(launchctl list \"${service}\" 2>/dev/null)\n\n if [[ $plist_status == \\{* ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo launchctl remove \"${service}\"\n else\n launchctl remove \"${service}\"\n fi\n sleep 1\n fi\n\n paths=(\n \"/Library/LaunchAgents/${service}.plist\"\n \"/Library/LaunchDaemons/${service}.plist\"\n )\n\n # if not using sudo, prepend the home directory to the paths\n if [[ $should_sudo == \"false\" ]]; then\n for i in \"${!paths[@]}\"; do\n paths[i]=\"${HOME}${paths[i]}\"\n done\n fi\n\n for path in \"${paths[@]}\"; do\n if [[ -e \"$path\" ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo rm -f -- \"$path\"\n else\n rm -f -- \"$path\"\n fi\n fi\n done\n done\n}\n\nremove_pkg_files() {\n local PKGID=\"$1\"\n expand_pkgid_and_map \"$PKGID\" remove_receipt_files\n}\n\nremove_receipt_files() {\n local PKGID=\"$1\"\n local PKGINFO VOLUME INSTALL_LOCATION FULL_INSTALL_LOCATION\n\n echo \"pkgutil --pkg-info-plist \\\"$PKGID\\\"\"\n PKGINFO=$(pkgutil --pkg-info-plist \"$PKGID\")\n VOLUME=$(echo \"$PKGINFO\" | awk '/<key>volume<\\/key>/ {getline; gsub(/.*<string>|<\\/string>.*/, \"\"); print}')\n INSTALL_LOCATION=$(echo \"$PKGINFO\" | awk '/<key>install-location<\\/key>/ {getline; gsub(/.*<string>|<\\/string>.*/, \"\"); print}')\n\n if [ -z \"$INSTALL_LOCATION\" ] || [ \"$INSTALL_LOCATION\" = \"/\" ]; then\n FULL_INSTALL_LOCATION=\"$VOLUME\"\n else\n FULL_INSTALL_LOCATION=\"$VOLUME/$INSTALL_LOCATION\"\n FULL_INSTALL_LOCATION=$(echo \"$FULL_INSTALL_LOCATION\" | sed 's|//|/|g')\n fi\n\n echo \"sudo pkgutil --only-files --files \\\"$PKGID\\\" | sed \\\"s|^|${FULL_INSTALL_LOCATION}/|\\\" | tr '\\\\\\\\n' '\\\\\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\"\n sudo pkgutil --only-files --files \"$PKGID\" | sed \"s|^|/${INSTALL_LOCATION}/|\" | tr '\\n' '\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\n\n echo \"sudo pkgutil --only-dirs --files \\\"$PKGID\\\" | sed \\\"s|^|${FULL_INSTALL_LOCATION}/|\\\" | grep '\\\\.app$' | tr '\\\\\\\\n' '\\\\\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\"\n sudo pkgutil --only-dirs --files \"$PKGID\" | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" | grep '\\.app$' | tr '\\n' '\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\n\n root_app_dir=$(\n sudo pkgutil --only-dirs --files \"$PKGID\" \\\n | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" \\\n | grep 'Applications' \\\n | awk '{ print length, $0 }' \\\n | sort -n \\\n | head -n1 \\\n | cut -d' ' -f2-\n )\n if [ -n \"$root_app_dir\" ]; then\n echo \"sudo rmdir -p \\\"$root_app_dir\\\" 2>/dev/null || :\"\n sudo rmdir -p \"$root_app_dir\" 2>/dev/null || :\n fi\n}\n\ntrash() {\n local logged_in_user=\"$1\"\n local target_file=\"$2\"\n local timestamp=\"$(date +%Y-%m-%d-%s)\"\n local rand=\"$(jot -r 1 0 99999)\"\n\n # replace ~ with /Users/$logged_in_user\n if [[ \"$target_file\" == ~* ]]; then\n target_file=\"/Users/$logged_in_user${target_file:1}\"\n fi\n\n local trash=\"/Users/$logged_in_user/.Trash\"\n\n # If the target contains glob characters, expand it and move each match.\n if [[ \"$target_file\" == *[*?[]* ]]; then\n local file file_name\n local matched=false\n local i=0\n # compgen -G expands the (quoted) pattern itself, so paths containing\n # spaces glob correctly; reading line by line keeps each match intact.\n while IFS= read -r file; do\n [[ -n \"$file\" ]] || continue\n [[ -e \"$file\" || -L \"$file\" ]] || continue\n matched=true\n i=$((i + 1))\n file_name=\"$(basename \"$file\")\"\n echo \"removing $file.\"\n # The per-match counter keeps matches that share a basename from\n # overwriting each other in the trash.\n mv -f \"$file\" \"$trash/${file_name}_${timestamp}_${rand}_${i}\"\n done < <(compgen -G \"$target_file\" 2>/dev/null)\n if [[ \"$matched\" == false ]]; then\n echo \"$target_file doesn't exist.\"\n fi\n return\n fi\n\n local file_name=\"$(basename \"${target_file}\")\"\n\n if [[ -e \"$target_file\" ]]; then\n echo \"removing $target_file.\"\n mv -f \"$target_file\" \"$trash/${file_name}_${timestamp}_${rand}\"\n else\n echo \"$target_file doesn't exist.\"\n fi\n}\n\nremove_launchctl_service 'com.microsoft.autoupdate.helper'\nremove_launchctl_service 'com.microsoft.autoupdate.helpertool'\nremove_launchctl_service 'com.microsoft.update.agent'\nquit_application 'com.microsoft.autoupdate.fba'\nquit_application 'com.microsoft.autoupdate2'\nquit_application 'com.microsoft.errorreporting'\nremove_pkg_files 'com.microsoft.package.Microsoft_AU_Bootstrapper.app'\nforget_pkg 'com.microsoft.package.Microsoft_AU_Bootstrapper.app'\nremove_pkg_files 'com.microsoft.package.Microsoft_AutoUpdate.app'\nforget_pkg 'com.microsoft.package.Microsoft_AutoUpdate.app'\nsudo rm -rf '/Library/Caches/com.microsoft.autoupdate.fba'\nsudo rm -rf '/Library/Caches/com.microsoft.autoupdate.helper'\nsudo rm -rf '/Library/LaunchDaemons/com.microsoft.autoupdate.helper.plist'\nsudo rm -rf '/Library/Preferences/com.microsoft.autoupdate2.plist'\nsudo rm -rf '/Library/PrivilegedHelperTools/com.microsoft.autoupdate.helper'\nsudo rmdir '~/Library/Caches/Microsoft'\nsudo rmdir '~/Library/Caches/Microsoft/uls'\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/UBF8T346G9.com.microsoft.oneauth'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Microsoft AutoUpdate'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.microsoft.autoupdate.fba'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.microsoft.autoupdate2'\ntrash $LOGGED_IN_USER '~/Library/Caches/Microsoft/uls/com.microsoft.autoupdate.fba'\ntrash $LOGGED_IN_USER '~/Library/Caches/Microsoft/uls/com.microsoft.autoupdate2'\ntrash $LOGGED_IN_USER '~/Library/Cookies/com.microsoft.autoupdate.fba.binarycookies'\ntrash $LOGGED_IN_USER '~/Library/Cookies/com.microsoft.autoupdate2.binarycookies'\ntrash $LOGGED_IN_USER '~/Library/Group Containers/UBF8T346G9.com.microsoft.oneauth'\ntrash $LOGGED_IN_USER '~/Library/Group Containers/UBF8T346G9.ms'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.microsoft.autoupdate.fba'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.microsoft.autoupdate2'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.microsoft.autoupdate.fba.plist'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.microsoft.autoupdate2.plist'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.microsoft.shared.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/com.microsoft.autoupdate2.savedState'\n",
"31e9e039": "#!/bin/bash\n\n# variables\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\nexpand_pkgid_and_map() {\n local PKGID=\"$1\"\n local FUNC=\"$2\"\n if [[ \"$PKGID\" == *\"*\" ]]; then\n local prefix=\"${PKGID%\\*}\"\n echo \"Expanding wildcard for PKGID: $PKGID\"\n for receipt in $(pkgutil --pkgs | grep \"^${prefix}\"); do\n echo \"Processing $receipt\"\n \"$FUNC\" \"$receipt\"\n done\n else\n \"$FUNC\" \"$PKGID\"\n fi\n}\n\nforget_pkg() {\n local PKGID=\"$1\"\n expand_pkgid_and_map \"$PKGID\" forget_receipt\n}\n\nforget_receipt() {\n local PKGID=\"$1\"\n sudo pkgutil --forget \"$PKGID\"\n}\n\nquit_application() {\n local bundle_id=\"$1\"\n local timeout_duration=10\n\n # check if the application is running\n local app_running\n app_running=$(osascript -e \"application id \\\"$bundle_id\\\" is running\" 2>/dev/null)\n if [[ \"$app_running\" != \"true\" ]]; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ -z \"$console_user\" || \"$console_user\" == \"root\" || \"$console_user\" == \"loginwindow\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" >/dev/null 2>&1; then\n if ! pgrep -f \"$bundle_id\" >/dev/null 2>&1; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n fi\n sleep 1\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n fi\n}\n\n\nremove_launchctl_service() {\n local service=\"$1\"\n local booleans=(\"true\" \"false\")\n local plist_status\n local paths\n local should_sudo\n\n echo \"Removing launchctl service ${service}\"\n\n # A wildcard label can't be used with launchctl or as a plist name, so expand\n # it to the labels of currently loaded services that match the pattern.\n local services=(\"$service\")\n if [[ \"$service\" == *\"*\"* ]]; then\n local regex\n # Escape regex metacharacters, turn '*' into '.*', and anchor the pattern so\n # it matches a full label rather than a substring.\n regex=$(printf '%s' \"$service\" | sed -e 's/[][(){}.^$+?|\\\\]/\\\\&/g' -e 's/\\*/.*/g')\n regex=\"^${regex}$\"\n services=()\n local id\n # Match every loaded job by label regardless of PID; launchctl list reports\n # loaded-but-not-running jobs with a \"-\" in the PID column.\n while read -r _ _ id; do\n [[ \"$id\" =~ $regex ]] && services+=(\"$id\")\n done < <(launchctl list 2>/dev/null | tail -n +2)\n if [[ ${#services[@]} -eq 0 ]]; then\n echo \"No loaded launchctl service matches ${service}\"\n return\n fi\n fi\n\n local service_label\n for service_label in \"${services[@]}\"; do\n for should_sudo in \"${booleans[@]}\"; do\n plist_status=$(launchctl list \"${service_label}\" 2>/dev/null)\n\n if [[ $plist_status == \\{* ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo launchctl remove \"${service_label}\"\n else\n launchctl remove \"${service_label}\"\n fi\n sleep 1\n fi\n\n paths=(\n \"/Library/LaunchAgents/${service_label}.plist\"\n \"/Library/LaunchDaemons/${service_label}.plist\"\n )\n\n # if not using sudo, prepend the home directory to the paths\n if [[ $should_sudo == \"false\" ]]; then\n for i in \"${!paths[@]}\"; do\n paths[i]=\"${HOME}${paths[i]}\"\n done\n fi\n\n for path in \"${paths[@]}\"; do\n if [[ -e \"$path\" ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo rm -f -- \"$path\"\n else\n rm -f -- \"$path\"\n fi\n fi\n done\n done\n done\n}\n\nremove_pkg_files() {\n local PKGID=\"$1\"\n expand_pkgid_and_map \"$PKGID\" remove_receipt_files\n}\n\nremove_receipt_files() {\n local PKGID=\"$1\"\n local PKGINFO VOLUME INSTALL_LOCATION FULL_INSTALL_LOCATION\n\n echo \"pkgutil --pkg-info-plist \\\"$PKGID\\\"\"\n PKGINFO=$(pkgutil --pkg-info-plist \"$PKGID\")\n VOLUME=$(echo \"$PKGINFO\" | awk '/<key>volume<\\/key>/ {getline; gsub(/.*<string>|<\\/string>.*/, \"\"); print}')\n INSTALL_LOCATION=$(echo \"$PKGINFO\" | awk '/<key>install-location<\\/key>/ {getline; gsub(/.*<string>|<\\/string>.*/, \"\"); print}')\n\n if [ -z \"$INSTALL_LOCATION\" ] || [ \"$INSTALL_LOCATION\" = \"/\" ]; then\n FULL_INSTALL_LOCATION=\"$VOLUME\"\n else\n FULL_INSTALL_LOCATION=\"$VOLUME/$INSTALL_LOCATION\"\n FULL_INSTALL_LOCATION=$(echo \"$FULL_INSTALL_LOCATION\" | sed 's|//|/|g')\n fi\n\n echo \"sudo pkgutil --only-files --files \\\"$PKGID\\\" | sed \\\"s|^|${FULL_INSTALL_LOCATION}/|\\\" | tr '\\\\\\\\n' '\\\\\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\"\n sudo pkgutil --only-files --files \"$PKGID\" | sed \"s|^|/${INSTALL_LOCATION}/|\" | tr '\\n' '\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\n\n echo \"sudo pkgutil --only-dirs --files \\\"$PKGID\\\" | sed \\\"s|^|${FULL_INSTALL_LOCATION}/|\\\" | grep '\\\\.app$' | tr '\\\\\\\\n' '\\\\\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\"\n sudo pkgutil --only-dirs --files \"$PKGID\" | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" | grep '\\.app$' | tr '\\n' '\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\n\n root_app_dir=$(\n sudo pkgutil --only-dirs --files \"$PKGID\" \\\n | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" \\\n | grep 'Applications' \\\n | awk '{ print length, $0 }' \\\n | sort -n \\\n | head -n1 \\\n | cut -d' ' -f2-\n )\n if [ -n \"$root_app_dir\" ]; then\n echo \"sudo rmdir -p \\\"$root_app_dir\\\" 2>/dev/null || :\"\n sudo rmdir -p \"$root_app_dir\" 2>/dev/null || :\n fi\n}\n\ntrash() {\n local logged_in_user=\"$1\"\n local target_file=\"$2\"\n local timestamp=\"$(date +%Y-%m-%d-%s)\"\n local rand=\"$(jot -r 1 0 99999)\"\n\n # replace ~ with /Users/$logged_in_user\n if [[ \"$target_file\" == ~* ]]; then\n target_file=\"/Users/$logged_in_user${target_file:1}\"\n fi\n\n local trash=\"/Users/$logged_in_user/.Trash\"\n\n # If the target contains glob characters, expand it and move each match.\n if [[ \"$target_file\" == *[*?[]* ]]; then\n local file file_name\n local matched=false\n local i=0\n # compgen -G expands the (quoted) pattern itself, so paths containing\n # spaces glob correctly; reading line by line keeps each match intact.\n while IFS= read -r file; do\n [[ -n \"$file\" ]] || continue\n [[ -e \"$file\" || -L \"$file\" ]] || continue\n matched=true\n i=$((i + 1))\n file_name=\"$(basename \"$file\")\"\n echo \"removing $file.\"\n # The per-match counter keeps matches that share a basename from\n # overwriting each other in the trash.\n mv -f \"$file\" \"$trash/${file_name}_${timestamp}_${rand}_${i}\"\n done < <(compgen -G \"$target_file\" 2>/dev/null)\n if [[ \"$matched\" == false ]]; then\n echo \"$target_file doesn't exist.\"\n fi\n return\n fi\n\n local file_name=\"$(basename \"${target_file}\")\"\n\n if [[ -e \"$target_file\" ]]; then\n echo \"removing $target_file.\"\n mv -f \"$target_file\" \"$trash/${file_name}_${timestamp}_${rand}\"\n else\n echo \"$target_file doesn't exist.\"\n fi\n}\n\nremove_launchctl_service 'com.microsoft.autoupdate.helper'\nremove_launchctl_service 'com.microsoft.autoupdate.helpertool'\nremove_launchctl_service 'com.microsoft.update.agent'\nquit_application 'com.microsoft.autoupdate.fba'\nquit_application 'com.microsoft.autoupdate2'\nquit_application 'com.microsoft.errorreporting'\nremove_pkg_files 'com.microsoft.package.Microsoft_AU_Bootstrapper.app'\nforget_pkg 'com.microsoft.package.Microsoft_AU_Bootstrapper.app'\nremove_pkg_files 'com.microsoft.package.Microsoft_AutoUpdate.app'\nforget_pkg 'com.microsoft.package.Microsoft_AutoUpdate.app'\nsudo rm -rf '/Library/Caches/com.microsoft.autoupdate.fba'\nsudo rm -rf '/Library/Caches/com.microsoft.autoupdate.helper'\nsudo rm -rf '/Library/LaunchDaemons/com.microsoft.autoupdate.helper.plist'\nsudo rm -rf '/Library/Preferences/com.microsoft.autoupdate2.plist'\nsudo rm -rf '/Library/PrivilegedHelperTools/com.microsoft.autoupdate.helper'\nsudo rmdir '~/Library/Caches/Microsoft'\nsudo rmdir '~/Library/Caches/Microsoft/uls'\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/UBF8T346G9.com.microsoft.oneauth'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Microsoft AutoUpdate'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.microsoft.autoupdate.fba'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.microsoft.autoupdate2'\ntrash $LOGGED_IN_USER '~/Library/Caches/Microsoft/uls/com.microsoft.autoupdate.fba'\ntrash $LOGGED_IN_USER '~/Library/Caches/Microsoft/uls/com.microsoft.autoupdate2'\ntrash $LOGGED_IN_USER '~/Library/Cookies/com.microsoft.autoupdate.fba.binarycookies'\ntrash $LOGGED_IN_USER '~/Library/Cookies/com.microsoft.autoupdate2.binarycookies'\ntrash $LOGGED_IN_USER '~/Library/Group Containers/UBF8T346G9.com.microsoft.oneauth'\ntrash $LOGGED_IN_USER '~/Library/Group Containers/UBF8T346G9.ms'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.microsoft.autoupdate.fba'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.microsoft.autoupdate2'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.microsoft.autoupdate.fba.plist'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.microsoft.autoupdate2.plist'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.microsoft.shared.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/com.microsoft.autoupdate2.savedState'\n",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Expand user-cache parent paths before removing them.

sudo rmdir '~/Library/Caches/Microsoft...' treats ~ literally, so these cleanup calls silently miss the logged-in user’s cache folders. Run them after the trash calls using /Users/$LOGGED_IN_USER/..., with uls before its parent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ee/maintained-apps/outputs/microsoft-auto-update/darwin.json` at line 19, The
cleanup calls in the Microsoft AutoUpdate script are using a literal tilde path,
so the user cache directories are not actually removed. Update the direct
removal logic in the script that defines LOGGED_IN_USER and the trailing cleanup
calls so they resolve to /Users/$LOGGED_IN_USER/... instead of using '~', and
keep the more specific uls directory removed before its parent Microsoft cache
folder.

],
"refs": {
"3b06f51c": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\nremove_launchctl_service() {\n local service=\"$1\"\n local booleans=(\"true\" \"false\")\n local plist_status\n local paths\n local should_sudo\n\n echo \"Removing launchctl service ${service}\"\n\n for should_sudo in \"${booleans[@]}\"; do\n plist_status=$(launchctl list \"${service}\" 2>/dev/null)\n\n if [[ $plist_status == \\{* ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo launchctl remove \"${service}\"\n else\n launchctl remove \"${service}\"\n fi\n sleep 1\n fi\n\n paths=(\n \"/Library/LaunchAgents/${service}.plist\"\n \"/Library/LaunchDaemons/${service}.plist\"\n )\n\n # if not using sudo, prepend the home directory to the paths\n if [[ $should_sudo == \"false\" ]]; then\n for i in \"${!paths[@]}\"; do\n paths[i]=\"${HOME}${paths[i]}\"\n done\n fi\n\n for path in \"${paths[@]}\"; do\n if [[ -e \"$path\" ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo rm -f -- \"$path\"\n else\n rm -f -- \"$path\"\n fi\n fi\n done\n done\n}\n\ntrash() {\n local logged_in_user=\"$1\"\n local target_file=\"$2\"\n local timestamp=\"$(date +%Y-%m-%d-%s)\"\n local rand=\"$(jot -r 1 0 99999)\"\n\n # replace ~ with /Users/$logged_in_user\n if [[ \"$target_file\" == ~* ]]; then\n target_file=\"/Users/$logged_in_user${target_file:1}\"\n fi\n\n local trash=\"/Users/$logged_in_user/.Trash\"\n\n # If the target contains glob characters, expand it and move each match.\n if [[ \"$target_file\" == *[*?[]* ]]; then\n local file file_name\n local matched=false\n local i=0\n # compgen -G expands the (quoted) pattern itself, so paths containing\n # spaces glob correctly; reading line by line keeps each match intact.\n while IFS= read -r file; do\n [[ -n \"$file\" ]] || continue\n [[ -e \"$file\" || -L \"$file\" ]] || continue\n matched=true\n i=$((i + 1))\n file_name=\"$(basename \"$file\")\"\n echo \"removing $file.\"\n # The per-match counter keeps matches that share a basename from\n # overwriting each other in the trash.\n mv -f \"$file\" \"$trash/${file_name}_${timestamp}_${rand}_${i}\"\n done < <(compgen -G \"$target_file\" 2>/dev/null)\n if [[ \"$matched\" == false ]]; then\n echo \"$target_file doesn't exist.\"\n fi\n return\n fi\n\n local file_name=\"$(basename \"${target_file}\")\"\n\n if [[ -e \"$target_file\" ]]; then\n echo \"removing $target_file.\"\n mv -f \"$target_file\" \"$trash/${file_name}_${timestamp}_${rand}\"\n else\n echo \"$target_file doesn't exist.\"\n fi\n}\n\nremove_launchctl_service 'com.microsoft.EdgeUpdater.wake'\nsudo rm -rf \"$APPDIR/Microsoft Edge.app\"\nsudo rmdir '~/Library/Application Support/Microsoft'\nsudo rmdir '~/Library/Microsoft'\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/com.microsoft.edgemac.wdgExtension'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Microsoft Edge'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Microsoft/EdgeUpdater'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.microsoft.edgemac'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.microsoft.EdgeUpdater'\ntrash $LOGGED_IN_USER '~/Library/Caches/Microsoft Edge'\ntrash $LOGGED_IN_USER '~/Library/Containers/com.microsoft.edgemac.wdgExtension'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.microsoft.edgemac'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.microsoft.EdgeUpdater'\ntrash $LOGGED_IN_USER '~/Library/LaunchAgents/com.microsoft.EdgeUpdater.*.plist'\ntrash $LOGGED_IN_USER '~/Library/Microsoft/MicrosoftSoftwareUpdate/Actives/com.microsoft.edgemac'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.microsoft.edgemac.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/com.microsoft.edgemac.savedState'\ntrash $LOGGED_IN_USER '~/Library/WebKit/com.microsoft.edgemac'\n",
"afe8f338": "#!/bin/bash\n\n# variables\nAPPDIR=\"/Applications/\"\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\nremove_launchctl_service() {\n local service=\"$1\"\n local booleans=(\"true\" \"false\")\n local plist_status\n local paths\n local should_sudo\n\n echo \"Removing launchctl service ${service}\"\n\n # A wildcard label can't be used with launchctl or as a plist name, so expand\n # it to the labels of currently loaded services that match the pattern.\n local services=(\"$service\")\n if [[ \"$service\" == *\"*\"* ]]; then\n local regex\n # Escape regex metacharacters, turn '*' into '.*', and anchor the pattern so\n # it matches a full label rather than a substring.\n regex=$(printf '%s' \"$service\" | sed -e 's/[][(){}.^$+?|\\\\]/\\\\&/g' -e 's/\\*/.*/g')\n regex=\"^${regex}$\"\n services=()\n local id\n # Match every loaded job by label regardless of PID; launchctl list reports\n # loaded-but-not-running jobs with a \"-\" in the PID column.\n while read -r _ _ id; do\n [[ \"$id\" =~ $regex ]] && services+=(\"$id\")\n done < <(launchctl list 2>/dev/null | tail -n +2)\n if [[ ${#services[@]} -eq 0 ]]; then\n echo \"No loaded launchctl service matches ${service}\"\n return\n fi\n fi\n\n local service_label\n for service_label in \"${services[@]}\"; do\n for should_sudo in \"${booleans[@]}\"; do\n plist_status=$(launchctl list \"${service_label}\" 2>/dev/null)\n\n if [[ $plist_status == \\{* ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo launchctl remove \"${service_label}\"\n else\n launchctl remove \"${service_label}\"\n fi\n sleep 1\n fi\n\n paths=(\n \"/Library/LaunchAgents/${service_label}.plist\"\n \"/Library/LaunchDaemons/${service_label}.plist\"\n )\n\n # if not using sudo, prepend the home directory to the paths\n if [[ $should_sudo == \"false\" ]]; then\n for i in \"${!paths[@]}\"; do\n paths[i]=\"${HOME}${paths[i]}\"\n done\n fi\n\n for path in \"${paths[@]}\"; do\n if [[ -e \"$path\" ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo rm -f -- \"$path\"\n else\n rm -f -- \"$path\"\n fi\n fi\n done\n done\n done\n}\n\ntrash() {\n local logged_in_user=\"$1\"\n local target_file=\"$2\"\n local timestamp=\"$(date +%Y-%m-%d-%s)\"\n local rand=\"$(jot -r 1 0 99999)\"\n\n # replace ~ with /Users/$logged_in_user\n if [[ \"$target_file\" == ~* ]]; then\n target_file=\"/Users/$logged_in_user${target_file:1}\"\n fi\n\n local trash=\"/Users/$logged_in_user/.Trash\"\n\n # If the target contains glob characters, expand it and move each match.\n if [[ \"$target_file\" == *[*?[]* ]]; then\n local file file_name\n local matched=false\n local i=0\n # compgen -G expands the (quoted) pattern itself, so paths containing\n # spaces glob correctly; reading line by line keeps each match intact.\n while IFS= read -r file; do\n [[ -n \"$file\" ]] || continue\n [[ -e \"$file\" || -L \"$file\" ]] || continue\n matched=true\n i=$((i + 1))\n file_name=\"$(basename \"$file\")\"\n echo \"removing $file.\"\n # The per-match counter keeps matches that share a basename from\n # overwriting each other in the trash.\n mv -f \"$file\" \"$trash/${file_name}_${timestamp}_${rand}_${i}\"\n done < <(compgen -G \"$target_file\" 2>/dev/null)\n if [[ \"$matched\" == false ]]; then\n echo \"$target_file doesn't exist.\"\n fi\n return\n fi\n\n local file_name=\"$(basename \"${target_file}\")\"\n\n if [[ -e \"$target_file\" ]]; then\n echo \"removing $target_file.\"\n mv -f \"$target_file\" \"$trash/${file_name}_${timestamp}_${rand}\"\n else\n echo \"$target_file doesn't exist.\"\n fi\n}\n\nremove_launchctl_service 'com.microsoft.EdgeUpdater.wake'\nsudo rm -rf \"$APPDIR/Microsoft Edge.app\"\nsudo rmdir '~/Library/Application Support/Microsoft'\nsudo rmdir '~/Library/Microsoft'\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/com.microsoft.edgemac.wdgExtension'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Microsoft Edge'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Microsoft/EdgeUpdater'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.microsoft.edgemac'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.microsoft.EdgeUpdater'\ntrash $LOGGED_IN_USER '~/Library/Caches/Microsoft Edge'\ntrash $LOGGED_IN_USER '~/Library/Containers/com.microsoft.edgemac.wdgExtension'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.microsoft.edgemac'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.microsoft.EdgeUpdater'\ntrash $LOGGED_IN_USER '~/Library/LaunchAgents/com.microsoft.EdgeUpdater.*.plist'\ntrash $LOGGED_IN_USER '~/Library/Microsoft/MicrosoftSoftwareUpdate/Actives/com.microsoft.edgemac'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.microsoft.edgemac.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/com.microsoft.edgemac.savedState'\ntrash $LOGGED_IN_USER '~/Library/WebKit/com.microsoft.edgemac'\n",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Expand user-library parent paths before removing them.

sudo rmdir '~/Library/...' treats ~ literally, and these parent removals run before the child trash calls. Move them after the relevant trash calls and target /Users/$LOGGED_IN_USER/....

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ee/maintained-apps/outputs/microsoft-edge/darwin.json` at line 19, The parent
directory removals in the Edge cleanup script are using quoted tilde paths, so
`sudo rmdir` treats them literally and runs before the child cleanup work.
Update the cleanup flow around the `remove_launchctl_service`/`trash` calls so
those parent directories are removed only after their contents are trashed, and
change the `rmdir` targets to use the resolved `/Users/$LOGGED_IN_USER/...` form
instead of quoted `~` paths.

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

CI Feedback 🧐

A test triggered by this PR failed. Here is an AI-generated analysis of the failure:

Action: test-fma-pr-only

Failed stage: Filter apps.json and verify changed apps [❌]

Failed test name: ""

Failure summary:

The action failed because the app validation step exited non-zero (exit status 1) after encountering
errors while validating maintained apps.
- AdGuard (adguard/darwin) failed to download its
installer: reading https://static.adguard.com/mac/release/AdGuard-2.18.0.2089.dmg contents timed out
(context deadline exceeded).
- Gemini (google-gemini/darwin) failed version validation: expected
version 1.80.15.516, but osquery found 1.80.18.522, resulting in App version '1.80.15.516' was not
found by osquery.
The validator reported Validated 121 out of 123 apps successfully and Apps with
errors: [AdGuard Gemini], which caused the step to terminate with exit code 1.

Relevant error logs:
1:  ##[group]Runner Image Provisioner
2:  Hosted Compute Agent
...

168:  * [new branch]            11938-loadtesting-branch    -> origin/11938-loadtesting-branch
169:  * [new branch]            12007-ui-idp-metadata       -> origin/12007-ui-idp-metadata
170:  * [new branch]            12351-try-fix-for-fleet-desktop -> origin/12351-try-fix-for-fleet-desktop
171:  * [new branch]            12356-puppet-callback       -> origin/12356-puppet-callback
172:  * [new branch]            12375-query-bulk-action     -> origin/12375-query-bulk-action
173:  * [new branch]            12474-documentation         -> origin/12474-documentation
174:  * [new branch]            12614-federated-auth-support -> origin/12614-federated-auth-support
175:  * [new branch]            12687-host-issues-query     -> origin/12687-host-issues-query
176:  * [new branch]            12696-loadtest-cis-changes  -> origin/12696-loadtest-cis-changes
177:  * [new branch]            12842-fleetd-bitlocker-management -> origin/12842-fleetd-bitlocker-management
178:  * [new branch]            12842-fleetd-bitlocker-table -> origin/12842-fleetd-bitlocker-table
179:  * [new branch]            12889-faster-software       -> origin/12889-faster-software
180:  * [new branch]            13287-loadtest-changes      -> origin/13287-loadtest-changes
181:  * [new branch]            13288-poc-fleet-fips        -> origin/13288-poc-fleet-fips
182:  * [new branch]            13485-query-reports-migration -> origin/13485-query-reports-migration
183:  * [new branch]            13574-cache-failed-policy-counts -> origin/13574-cache-failed-policy-counts
184:  * [new branch]            13657-docs                  -> origin/13657-docs
...

196:  * [new branch]            146orch                     -> origin/146orch
197:  * [new branch]            14753-windows-ps1-api       -> origin/14753-windows-ps1-api
198:  * [new branch]            14957-test-dep-screen       -> origin/14957-test-dep-screen
199:  * [new branch]            14957-test-dep-screen-patch -> origin/14957-test-dep-screen-patch
200:  * [new branch]            15068-disk-encryption-ii    -> origin/15068-disk-encryption-ii
201:  * [new branch]            15082-download-api-docs     -> origin/15082-download-api-docs
202:  * [new branch]            15082-make-endpoints-consistent -> origin/15082-make-endpoints-consistent
203:  * [new branch]            15082-update-download-endpoints -> origin/15082-update-download-endpoints
204:  * [new branch]            15380-hosts-api-doc         -> origin/15380-hosts-api-doc
205:  * [new branch]            15461-ui-dep-assign-profile-responses -> origin/15461-ui-dep-assign-profile-responses
206:  * [new branch]            15463-msi-fix               -> origin/15463-msi-fix
207:  * [new branch]            15559-move-scep             -> origin/15559-move-scep
208:  * [new branch]            15633                       -> origin/15633
209:  * [new branch]            15770-mac-os-vuln           -> origin/15770-mac-os-vuln
210:  * [new branch]            15801-migration             -> origin/15801-migration
211:  * [new branch]            15912-password-reset-error-code -> origin/15912-password-reset-error-code
212:  * [new branch]            16182-log-root              -> origin/16182-log-root
...

618:  * [new branch]            4701-tickets                -> origin/4701-tickets
619:  * [new branch]            4701-with-osqpfix           -> origin/4701-with-osqpfix
620:  * [new branch]            471-0716                    -> origin/471-0716
621:  * [new branch]            471-cloudfront              -> origin/471-cloudfront
622:  * [new branch]            471-tickets                 -> origin/471-tickets
623:  * [new branch]            4710mdm                     -> origin/4710mdm
624:  * [new branch]            4710mdmloadtest             -> origin/4710mdmloadtest
625:  * [new branch]            4711-bsln                   -> origin/4711-bsln
626:  * [new branch]            471RC-07-09                 -> origin/471RC-07-09
627:  * [new branch]            4720mdm                     -> origin/4720mdm
628:  * [new branch]            4720orch                    -> origin/4720orch
629:  * [new branch]            4720orch1                   -> origin/4720orch1
630:  * [new branch]            4720orch2                   -> origin/4720orch2
631:  * [new branch]            47289-new-fma-masv          -> origin/47289-new-fma-masv
632:  * [new branch]            472orchm                    -> origin/472orchm
633:  * [new branch]            47303-redis-moved-errors-from-getqueryresultscounts-incrqueryresultscounts-in-cluster-mode -> origin/47303-redis-moved-errors-from-getqueryresultscounts-incrqueryresultscounts-in-cluster-mode
634:  * [new branch]            4735orch                    -> origin/4735orch
...

663:  * [new branch]            7765-frontend               -> origin/7765-frontend
664:  * [new branch]            7766-backend-final-main-merge -> origin/7766-backend-final-main-merge
665:  * [new branch]            7766-backend-test           -> origin/7766-backend-test
666:  * [new branch]            7766-frontend               -> origin/7766-frontend
667:  * [new branch]            7993-figure-out-the-limits-of-our-current-search-approach -> origin/7993-figure-out-the-limits-of-our-current-search-approach
668:  * [new branch]            8021-orbit-use-specified-identifier -> origin/8021-orbit-use-specified-identifier
669:  * [new branch]            8186-vulnerable-software-false-positive-docker -> origin/8186-vulnerable-software-false-positive-docker
670:  * [new branch]            8593-gitops                 -> origin/8593-gitops
671:  * [new branch]            8708-docs                   -> origin/8708-docs
672:  * [new branch]            8708-encryption-key-api     -> origin/8708-encryption-key-api
673:  * [new branch]            8708-ingest-file-vault      -> origin/8708-ingest-file-vault
674:  * [new branch]            8708-ingest-macos-encription-key -> origin/8708-ingest-macos-encription-key
675:  * [new branch]            8708-ingest-macos-encription-key-ii -> origin/8708-ingest-macos-encription-key-ii
676:  * [new branch]            8974-native-smbios-uuid-support -> origin/8974-native-smbios-uuid-support
677:  * [new branch]            9260-cis-checks-5.10.x      -> origin/9260-cis-checks-5.10.x
678:  * [new branch]            9394-failed-to-create-device-auth-token-due-to-duplicate-key-errors -> origin/9394-failed-to-create-device-auth-token-due-to-duplicate-key-errors
679:  * [new branch]            9590-schema                 -> origin/9590-schema
...

869:  * [new branch]            allenhouchins-add-hardware-procurement-info -> origin/allenhouchins-add-hardware-procurement-info
870:  * [new branch]            allenhouchins-article-formatting-update -> origin/allenhouchins-article-formatting-update
871:  * [new branch]            allenhouchins-contour-normalize -> origin/allenhouchins-contour-normalize
872:  * [new branch]            allenhouchins-fma-installer-arch -> origin/allenhouchins-fma-installer-arch
873:  * [new branch]            allenhouchins-net-arm64     -> origin/allenhouchins-net-arm64
874:  * [new branch]            allenhouchins-new-blog-post -> origin/allenhouchins-new-blog-post
875:  * [new branch]            allenhouchins-patch-1       -> origin/allenhouchins-patch-1
876:  * [new branch]            allenhouchins-santa-table-updates -> origin/allenhouchins-santa-table-updates
877:  * [new branch]            allenhouchins-update-custom-tap -> origin/allenhouchins-update-custom-tap
878:  * [new branch]            allenhouchins-update-new-fma-skill -> origin/allenhouchins-update-new-fma-skill
879:  * [new branch]            always-create-event-next-thu -> origin/always-create-event-next-thu
880:  * [new branch]            and_osq_proj                -> origin/and_osq_proj
881:  * [new branch]            andrey/loadtest-oval-vulns  -> origin/andrey/loadtest-oval-vulns
882:  * [new branch]            android-cofniguration-profiles-research -> origin/android-cofniguration-profiles-research
883:  * [new branch]            android-enroll-note         -> origin/android-enroll-note
884:  * [new branch]            android-enrollment-error    -> origin/android-enrollment-error
885:  * [new branch]            android-fleet-vars          -> origin/android-fleet-vars
...

938:  * [new branch]            await-configuration-docs    -> origin/await-configuration-docs
939:  * [new branch]            aws-licensing-integration-proposal -> origin/aws-licensing-integration-proposal
940:  * [new branch]            backport-a41fb63            -> origin/backport-a41fb63
941:  * [new branch]            backport-c02af              -> origin/backport-c02af
942:  * [new branch]            backport-e65d6cf            -> origin/backport-e65d6cf
943:  * [new branch]            backport-ef07a406cc8dbccb4914bbd27631df74e5b13b49 -> origin/backport-ef07a406cc8dbccb4914bbd27631df74e5b13b49
944:  * [new branch]            backport-vpp-fix            -> origin/backport-vpp-fix
945:  * [new branch]            base-software-titles-loadtest -> origin/base-software-titles-loadtest
946:  * [new branch]            base-software-titles-loadtest-2 -> origin/base-software-titles-loadtest-2
947:  * [new branch]            beekeeper-fma-initial       -> origin/beekeeper-fma-initial
948:  * [new branch]            bettapizza                  -> origin/bettapizza
949:  * [new branch]            bettapizza--ghost-testimonial-update -> origin/bettapizza--ghost-testimonial-update
950:  * [new branch]            bettapizza-patch-1          -> origin/bettapizza-patch-1
951:  * [new branch]            bettapizza-patch-2          -> origin/bettapizza-patch-2
952:  * [new branch]            bettapizza-patch-3          -> origin/bettapizza-patch-3
953:  * [new branch]            better-byod-error           -> origin/better-byod-error
954:  * [new branch]            bounded-context-proposals   -> origin/bounded-context-proposals
...

989:  * [new branch]            cherry-pick-48012-to-docs-v4.89.0 -> origin/cherry-pick-48012-to-docs-v4.89.0
990:  * [new branch]            cherry-pick-48561-into-4.89 -> origin/cherry-pick-48561-into-4.89
991:  * [new branch]            cherry-pick-8750-into-rc-minor-fleet-v4.60.0 -> origin/cherry-pick-8750-into-rc-minor-fleet-v4.60.0
992:  * [new branch]            cherry-pick-dogfood-change  -> origin/cherry-pick-dogfood-change
993:  * [new branch]            cherry-pick-dogfood-env-vars -> origin/cherry-pick-dogfood-env-vars
994:  * [new branch]            cherry-pick-fleetctl-changes-to-allow-testing -> origin/cherry-pick-fleetctl-changes-to-allow-testing
995:  * [new branch]            cherry-pick-mac-address-vital -> origin/cherry-pick-mac-address-vital
996:  * [new branch]            cherry-pick-patches         -> origin/cherry-pick-patches
997:  * [new branch]            cherry-pick-remove-overrides-in-dev-mode -> origin/cherry-pick-remove-overrides-in-dev-mode
998:  * [new branch]            cherry-pick-util            -> origin/cherry-pick-util
999:  * [new branch]            cherry-pick-webhooks        -> origin/cherry-pick-webhooks
1000:  * [new branch]            cherrypick-v4.85.0-version-bump-into-main -> origin/cherrypick-v4.85.0-version-bump-into-main
1001:  * [new branch]            chore-UI-update-for-CSP     -> origin/chore-UI-update-for-CSP
1002:  * [new branch]            chore-add-specific-yarn-version -> origin/chore-add-specific-yarn-version
1003:  * [new branch]            chore-backend-api-patterns  -> origin/chore-backend-api-patterns
1004:  * [new branch]            chore-change-error-message-custom-profile -> origin/chore-change-error-message-custom-profile
1005:  * [new branch]            chore-cp-chage-status-code-turn-off-mdm -> origin/chore-cp-chage-status-code-turn-off-mdm
...

1193:  * [new branch]            edwardsb-vuln-processing-fix-sandcastle -> origin/edwardsb-vuln-processing-fix-sandcastle
1194:  * [new branch]            edwardsb-vuln-processing-volume-fix -> origin/edwardsb-vuln-processing-volume-fix
1195:  * [new branch]            edwardsb/deployment-guide-updates -> origin/edwardsb/deployment-guide-updates
1196:  * [new branch]            edwardsb/restrict_console_access -> origin/edwardsb/restrict_console_access
1197:  * [new branch]            elastic_rum                 -> origin/elastic_rum
1198:  * [new branch]            enable-jit-by-default       -> origin/enable-jit-by-default
1199:  * [new branch]            enable-test-go-automation-macos -> origin/enable-test-go-automation-macos
1200:  * [new branch]            enforce-firefox-doh         -> origin/enforce-firefox-doh
1201:  * [new branch]            enforce-firefox-windows     -> origin/enforce-firefox-windows
1202:  * [new branch]            enforce-ios-26.5-update-deadline-july-2026 -> origin/enforce-ios-26.5-update-deadline-july-2026
1203:  * [new branch]            enforce-iso27001-workstations -> origin/enforce-iso27001-workstations
1204:  * [new branch]            enforce-macos-15.5-update-deadline -> origin/enforce-macos-15.5-update-deadline
1205:  * [new branch]            enforce-macos-version-workstations -> origin/enforce-macos-version-workstations
1206:  * [new branch]            enroll-android-feature-guide -> origin/enroll-android-feature-guide
1207:  * [new branch]            ericswenson0-patch-1        -> origin/ericswenson0-patch-1
1208:  * [new branch]            error-counts                -> origin/error-counts
1209:  * [new branch]            errors-perf-testing         -> origin/errors-perf-testing
1210:  * [new branch]            es-reports                  -> origin/es-reports
...

1226:  * [new branch]            eugkuo-patch-6-slugs-FMA    -> origin/eugkuo-patch-6-slugs-FMA
1227:  * [new branch]            example-fleetctl-profiles   -> origin/example-fleetctl-profiles
1228:  * [new branch]            exp-mdbook-docs             -> origin/exp-mdbook-docs
1229:  * [new branch]            experiment-disable-some-deletes -> origin/experiment-disable-some-deletes
1230:  * [new branch]            experiment-hardcode-counters-to-zero -> origin/experiment-hardcode-counters-to-zero
1231:  * [new branch]            experiment-no-txs           -> origin/experiment-no-txs
1232:  * [new branch]            experiment-status-tooltip   -> origin/experiment-status-tooltip
1233:  * [new branch]            experiment-with-two-branches -> origin/experiment-with-two-branches
1234:  * [new branch]            experimental_moveitem       -> origin/experimental_moveitem
1235:  * [new branch]            experimient-no-counts       -> origin/experimient-no-counts
1236:  * [new branch]            expiredcertstool            -> origin/expiredcertstool
1237:  * [new branch]            express-vpn-win             -> origin/express-vpn-win
1238:  * [new branch]            extra-dep-logs              -> origin/extra-dep-logs
1239:  * [new branch]            f84ee4c5f4                  -> origin/f84ee4c5f4
1240:  * [new branch]            faf-doc-clarification       -> origin/faf-doc-clarification
1241:  * [new branch]            failing-test                -> origin/failing-test
1242:  * [new branch]            faq-updates-2026-06         -> origin/faq-updates-2026-06
...

1282:  * [new branch]            feature-hydrant-ca          -> origin/feature-hydrant-ca
1283:  * [new branch]            feature-idp-byod            -> origin/feature-idp-byod
1284:  * [new branch]            feature-orbit-find          -> origin/feature-orbit-find
1285:  * [new branch]            feature-prioritization      -> origin/feature-prioritization
1286:  * [new branch]            feature-v4.44.1-live-queries -> origin/feature-v4.44.1-live-queries
1287:  * [new branch]            feature-windows-mdm-policy-extraction -> origin/feature-windows-mdm-policy-extraction
1288:  * [new branch]            feature/add-blank-to-osquer-link -> origin/feature/add-blank-to-osquer-link
1289:  * [new branch]            feature/fleet-macos-password-sync -> origin/feature/fleet-macos-password-sync
1290:  * [new branch]            feature_14722-activity-feed-webhooks -> origin/feature_14722-activity-feed-webhooks
1291:  * [new branch]            feature_18115-crit-vuln-issues -> origin/feature_18115-crit-vuln-issues
1292:  * [new branch]            feature_19010-ipad-ios-lock-wipe-backend-changes -> origin/feature_19010-ipad-ios-lock-wipe-backend-changes
1293:  * [new branch]            feature_22078               -> origin/feature_22078
1294:  * [new branch]            fix                         -> origin/fix
1295:  * [new branch]            fix-35118-cis-win10-v4      -> origin/fix-35118-cis-win10-v4
1296:  * [new branch]            fix-4.84.4-k8s              -> origin/fix-4.84.4-k8s
1297:  * [new branch]            fix-40083-failed-install-retries -> origin/fix-40083-failed-install-retries
1298:  * [new branch]            fix-41290-vpp-added-status  -> origin/fix-41290-vpp-added-status
1299:  * [new branch]            fix-41337-save-env-secrets-dry-run -> origin/fix-41337-save-env-secrets-dry-run
1300:  * [new branch]            fix-42901-ticker-leak       -> origin/fix-42901-ticker-leak
1301:  * [new branch]            fix-43623-fma-patch-policy-gitops -> origin/fix-43623-fma-patch-policy-gitops
1302:  * [new branch]            fix-44199-embedded-app-bundles -> origin/fix-44199-embedded-app-bundles
1303:  * [new branch]            fix-44325-gitops-mode-tooltips -> origin/fix-44325-gitops-mode-tooltips
1304:  * [new branch]            fix-47388-getclientconfig-nil-map -> origin/fix-47388-getclientconfig-nil-map
1305:  * [new branch]            fix-android-unenroll-activities -> origin/fix-android-unenroll-activities
1306:  * [new branch]            fix-blockquotes-google-groups -> origin/fix-blockquotes-google-groups
1307:  * [new branch]            fix-ca-verdict-spoof-16386  -> origin/fix-ca-verdict-spoof-16386
1308:  * [new branch]            fix-case-in-deploy-config-docs -> origin/fix-case-in-deploy-config-docs
1309:  * [new branch]            fix-cert-details-modal      -> origin/fix-cert-details-modal
1310:  * [new branch]            fix-checkerboard-scroll-to-right -> origin/fix-checkerboard-scroll-to-right
1311:  * [new branch]            fix-checkerboard-tooltip-nowrap -> origin/fix-checkerboard-tooltip-nowrap
1312:  * [new branch]            fix-cloudflare-warp-fma-output-json -> origin/fix-cloudflare-warp-fma-output-json
1313:  * [new branch]            fix-codeql-ci-error         -> origin/fix-codeql-ci-error
1314:  * [new branch]            fix-colima                  -> origin/fix-colima
1315:  * [new branch]            fix-color-for-mobile-sticky-nav -> origin/fix-color-for-mobile-sticky-nav
1316:  * [new branch]            fix-conditional-access-observer-authz -> origin/fix-conditional-access-observer-authz
1317:  * [new branch]            fix-conflict-in-feature_19010-ipad-ios-lock-wipe -> origin/fix-conflict-in-feature_19010-ipad-ios-lock-wipe
1318:  * [new branch]            fix-contrib-docs            -> origin/fix-contrib-docs
1319:  * [new branch]            fix-cve-2026-2792-firefox-esr -> origin/fix-cve-2026-2792-firefox-esr
1320:  * [new branch]            fix-cve-validate-enrichment-canary -> origin/fix-cve-validate-enrichment-canary
1321:  * [new branch]            fix-docker-cleanup-quay-nonfatal -> origin/fix-docker-cleanup-quay-nonfatal
1322:  * [new branch]            fix-docker-publish          -> origin/fix-docker-publish
1323:  * [new branch]            fix-dogfood-workstation     -> origin/fix-dogfood-workstation
1324:  * [new branch]            fix-embedded-bundle-titles-44199 -> origin/fix-embedded-bundle-titles-44199
1325:  * [new branch]            fix-firefox-cve-2026-2792   -> origin/fix-firefox-cve-2026-2792
1326:  * [new branch]            fix-firefox-fma-conflict-error-msg -> origin/fix-firefox-fma-conflict-error-msg
1327:  * [new branch]            fix-flakey-orbit-test       -> origin/fix-flakey-orbit-test
...

1333:  * [new branch]            fix-host-counts-vpp-software -> origin/fix-host-counts-vpp-software
1334:  * [new branch]            fix-instability-ubuntu-latest -> origin/fix-instability-ubuntu-latest
1335:  * [new branch]            fix-ios-ipados-enrollment-label-42721 -> origin/fix-ios-ipados-enrollment-label-42721
1336:  * [new branch]            fix-list-sw-permissions     -> origin/fix-list-sw-permissions
1337:  * [new branch]            fix-mdm-enrolled-webhook    -> origin/fix-mdm-enrolled-webhook
1338:  * [new branch]            fix-mdm-redirect            -> origin/fix-mdm-redirect
1339:  * [new branch]            fix-mdm-url-truncation      -> origin/fix-mdm-url-truncation
1340:  * [new branch]            fix-missing-test-schema-update -> origin/fix-missing-test-schema-update
1341:  * [new branch]            fix-modernize               -> origin/fix-modernize
1342:  * [new branch]            fix-no-space-left-on-device-failure -> origin/fix-no-space-left-on-device-failure
1343:  * [new branch]            fix-notarization            -> origin/fix-notarization
1344:  * [new branch]            fix-orbit-notarization      -> origin/fix-orbit-notarization
1345:  * [new branch]            fix-os-settings-name-col-truncation -> origin/fix-os-settings-name-col-truncation
1346:  * [new branch]            fix-os-settings-underline-descender-clip -> origin/fix-os-settings-underline-descender-clip
1347:  * [new branch]            fix-pricing-more-info-icon-documentationurl -> origin/fix-pricing-more-info-icon-documentationurl
1348:  * [new branch]            fix-receive-from-github-error -> origin/fix-receive-from-github-error
1349:  * [new branch]            fix-sandbox-local           -> origin/fix-sandbox-local
...

1363:  * [new branch]            fix-ui-typo-fleet-maintained-empty -> origin/fix-ui-typo-fleet-maintained-empty
1364:  * [new branch]            fix-vpp-edit-fleet-desktop-message -> origin/fix-vpp-edit-fleet-desktop-message
1365:  * [new branch]            fix-whatsapp-version-check  -> origin/fix-whatsapp-version-check
1366:  * [new branch]            fix-win-os-version          -> origin/fix-win-os-version
1367:  * [new branch]            fix_14825                   -> origin/fix_14825
1368:  * [new branch]            fixes-on-minor-fleet-v4.54.0 -> origin/fixes-on-minor-fleet-v4.54.0
1369:  * [new branch]            fixes-plus-4.35.1           -> origin/fixes-plus-4.35.1
1370:  * [new branch]            flaky-tests-nov-6           -> origin/flaky-tests-nov-6
1371:  * [new branch]            fleet-desktop-linux-browser -> origin/fleet-desktop-linux-browser
1372:  * [new branch]            fleet-desktop-version       -> origin/fleet-desktop-version
1373:  * [new branch]            fleet-mdm-server-url        -> origin/fleet-mdm-server-url
1374:  * [new branch]            fleet-multiple-abm-vpp-proposal -> origin/fleet-multiple-abm-vpp-proposal
1375:  * [new branch]            fleet-premium-users-usage-statistics -> origin/fleet-premium-users-usage-statistics
1376:  * [new branch]            fleet-release-infra         -> origin/fleet-release-infra
1377:  * [new branch]            fleet-server-config-1817    -> origin/fleet-server-config-1817
1378:  * [new branch]            fleet-server-log-request-errors -> origin/fleet-server-log-request-errors
1379:  * [new branch]            fleet-ship-rebuild          -> origin/fleet-ship-rebuild
...

2387:  * [new branch]            hotfix-failing-tests        -> origin/hotfix-failing-tests
2388:  * [new branch]            hotfix-query-update         -> origin/hotfix-query-update
2389:  * [new branch]            hotfix-revert-sofa-tables   -> origin/hotfix-revert-sofa-tables
2390:  * [new branch]            hotfix-v4.67.2-migration    -> origin/hotfix-v4.67.2-migration
2391:  * [new branch]            hotfix-v4.76.1              -> origin/hotfix-v4.76.1
2392:  * [new branch]            hover-gravatar              -> origin/hover-gravatar
2393:  * [new branch]            hughestaylor-patch-1        -> origin/hughestaylor-patch-1
2394:  * [new branch]            hughestaylor-patch-1-1      -> origin/hughestaylor-patch-1-1
2395:  * [new branch]            ignore-fleetctl-vulnerabilities -> origin/ignore-fleetctl-vulnerabilities
2396:  * [new branch]            improve-flakey-test         -> origin/improve-flakey-test
2397:  * [new branch]            improve-prometheus-metrics  -> origin/improve-prometheus-metrics
2398:  * [new branch]            improve-settings-subnav     -> origin/improve-settings-subnav
2399:  * [new branch]            include-mnt-in-gads-metric  -> origin/include-mnt-in-gads-metric
2400:  * [new branch]            increase-goreleaser-timeout-to-60m -> origin/increase-goreleaser-timeout-to-60m
2401:  * [new branch]            ingress-gateway-support     -> origin/ingress-gateway-support
2402:  * [new branch]            insert-software-installers-retry-error -> origin/insert-software-installers-retry-error
2403:  * [new branch]            install-all-recently-installed -> origin/install-all-recently-installed
...

2408:  * [new branch]            ireedy-patch-2              -> origin/ireedy-patch-2
2409:  * [new branch]            ireedy-patch-3              -> origin/ireedy-patch-3
2410:  * [new branch]            irena-deebradelo            -> origin/irena-deebradelo
2411:  * [new branch]            irena-handbook-edit         -> origin/irena-handbook-edit
2412:  * [new branch]            irena-test-with-john        -> origin/irena-test-with-john
2413:  * [new branch]            irenareedy-event-process    -> origin/irenareedy-event-process
2414:  * [new branch]            irenareedy-patch-1          -> origin/irenareedy-patch-1
2415:  * [new branch]            irenareedy-patch-2          -> origin/irenareedy-patch-2
2416:  * [new branch]            irenareedy-patch-4          -> origin/irenareedy-patch-4
2417:  * [new branch]            irenareedy-thumbtack-case-study -> origin/irenareedy-thumbtack-case-study
2418:  * [new branch]            iso-27001-compliance        -> origin/iso-27001-compliance
2419:  * [new branch]            iso42001-ai-discovery-inventory -> origin/iso42001-ai-discovery-inventory
2420:  * [new branch]            issue-11545-my-device-dep-modal -> origin/issue-11545-my-device-dep-modal
2421:  * [new branch]            issue-2716-specify-query-platforms -> origin/issue-2716-specify-query-platforms
2422:  * [new branch]            issue-3271-rate-limit-distributed-writes -> origin/issue-3271-rate-limit-distributed-writes
2423:  * [new branch]            issue-4361-mail-change-should-error -> origin/issue-4361-mail-change-should-error
2424:  * [new branch]            issue-conf-1968-delete-uninstalled-software-from-main -> origin/issue-conf-1968-delete-uninstalled-software-from-main
...

2633:  * [new branch]            melpike-api-apple-declarations-38986 -> origin/melpike-api-apple-declarations-38986
2634:  * [new branch]            melpike-api-default-fleet-windows-41787 -> origin/melpike-api-default-fleet-windows-41787
2635:  * [new branch]            melpike-api-device-url-43895 -> origin/melpike-api-device-url-43895
2636:  * [new branch]            melpike-api-minimum-version-39085 -> origin/melpike-api-minimum-version-39085
2637:  * [new branch]            melpike-api-py-script-41470 -> origin/melpike-api-py-script-41470
2638:  * [new branch]            melpike-audit-log-account-provisioning-45524 -> origin/melpike-audit-log-account-provisioning-45524
2639:  * [new branch]            melpike-audit-log-mdm-cmd-45021 -> origin/melpike-audit-log-mdm-cmd-45021
2640:  * [new branch]            melpike-audit-recovery-lock-password-rotate -> origin/melpike-audit-recovery-lock-password-rotate
2641:  * [new branch]            melpike-byod-wipe-lock-23242 -> origin/melpike-byod-wipe-lock-23242
2642:  * [new branch]            melpike-byod-wipe-lock-23242-1 -> origin/melpike-byod-wipe-lock-23242-1
2643:  * [new branch]            melpike-byod-wipe-lock-api-23242 -> origin/melpike-byod-wipe-lock-api-23242
2644:  * [new branch]            melpike-byod-wipe-lock-yaml-23242 -> origin/melpike-byod-wipe-lock-yaml-23242
2645:  * [new branch]            melpike-combine-guides-30674 -> origin/melpike-combine-guides-30674
2646:  * [new branch]            melpike-create-managed-local-account-guide-37141 -> origin/melpike-create-managed-local-account-guide-37141
2647:  * [new branch]            melpike-default-fleet-windows-41787 -> origin/melpike-default-fleet-windows-41787
2648:  * [new branch]            melpike-dep-error-messaging-43916 -> origin/melpike-dep-error-messaging-43916
2649:  * [new branch]            melpike-doc-deploy-sw-41470 -> origin/melpike-doc-deploy-sw-41470
...

2797:  * [new branch]            mna-doc-change-api-structure -> origin/mna-doc-change-api-structure
2798:  * [new branch]            mna-experiment-macos-profiles-uuid -> origin/mna-experiment-macos-profiles-uuid
2799:  * [new branch]            mna-fix-22558-windows-installer-stuck-pending -> origin/mna-fix-22558-windows-installer-stuck-pending
2800:  * [new branch]            mna-fix-duplicate-lock-unlock-activity -> origin/mna-fix-duplicate-lock-unlock-activity
2801:  * [new branch]            mna-fix-failing-tests       -> origin/mna-fix-failing-tests
2802:  * [new branch]            mna-fix-flaky-integration-test -> origin/mna-fix-flaky-integration-test
2803:  * [new branch]            mna-fix-oom-fleetctl-test   -> origin/mna-fix-oom-fleetctl-test
2804:  * [new branch]            mna-loadtest-enroll-host-limit -> origin/mna-loadtest-enroll-host-limit
2805:  * [new branch]            mna-orbit-node-key          -> origin/mna-orbit-node-key
2806:  * [new branch]            mna-revert-change-to-autogenerated-activities-doc -> origin/mna-revert-change-to-autogenerated-activities-doc
2807:  * [new branch]            mna-temp-osquery-perf-loadtest -> origin/mna-temp-osquery-perf-loadtest
2808:  * [new branch]            mna11997                    -> origin/mna11997
2809:  * [new branch]            mock-associate-assets-endpoint -> origin/mock-associate-assets-endpoint
2810:  * [new branch]            monitoring-test             -> origin/monitoring-test
2811:  * [new branch]            more-conditional-access-api-updates -> origin/more-conditional-access-api-updates
2812:  * [new branch]            more-fd-errors              -> origin/more-fd-errors
2813:  * [new branch]            move-conditional-access-script-to-testing-qa -> origin/move-conditional-access-script-to-testing-qa
...

3377:  * [new branch]            sgress454/34259-end-user-auth-setup-backend -> origin/sgress454/34259-end-user-auth-setup-backend
3378:  * [new branch]            sgress454/34528-end-user-auth-setup-agent -> origin/sgress454/34528-end-user-auth-setup-agent
3379:  * [new branch]            sgress454/35376             -> origin/sgress454/35376
3380:  * [new branch]            sgress454/35452             -> origin/sgress454/35452
3381:  * [new branch]            sgress454/37127-open-macos-end-user-auth-window -> origin/sgress454/37127-open-macos-end-user-auth-window
3382:  * [new branch]            sgress454/38063-skeleton-server -> origin/sgress454/38063-skeleton-server
3383:  * [new branch]            sgress454/39344-rename-teams-to-fleet-all-changes -> origin/sgress454/39344-rename-teams-to-fleet-all-changes
3384:  * [new branch]            sgress454/39344-rename-teams-to-fleet-api-changes -> origin/sgress454/39344-rename-teams-to-fleet-api-changes
3385:  * [new branch]            sgress454/40642-update-jit-saml-prefix -> origin/sgress454/40642-update-jit-saml-prefix
3386:  * [new branch]            sgress454/43482-re-pin      -> origin/sgress454/43482-re-pin
3387:  * [new branch]            sgress454/44077-disable-datasets-backend -> origin/sgress454/44077-disable-datasets-backend
3388:  * [new branch]            sgress454/44077-disable-datasets-docs -> origin/sgress454/44077-disable-datasets-docs
3389:  * [new branch]            sgress454/44077-disable-datasets-frontend -> origin/sgress454/44077-disable-datasets-frontend
3390:  * [new branch]            sgress454/45290-drop-mobile-from-charting -> origin/sgress454/45290-drop-mobile-from-charting
3391:  * [new branch]            sgress454/add-vex-for-cve-2026-23517 -> origin/sgress454/add-vex-for-cve-2026-23517
3392:  * [new branch]            sgress454/adr-for-error-codes -> origin/sgress454/adr-for-error-codes
3393:  * [new branch]            sgress454/adr-refactor-list-hosts -> origin/sgress454/adr-refactor-list-hosts
...

3445:  * [new branch]            software-titles-loadtest-2  -> origin/software-titles-loadtest-2
3446:  * [new branch]            solarized-theme             -> origin/solarized-theme
3447:  * [new branch]            solutions-folder            -> origin/solutions-folder
3448:  * [new branch]            sort-seen-hosts-before-inserting -> origin/sort-seen-hosts-before-inserting
3449:  * [new branch]            spalmesano0-patch-2         -> origin/spalmesano0-patch-2
3450:  * [new branch]            spike-fleet-lint-plugin     -> origin/spike-fleet-lint-plugin
3451:  * [new branch]            spike-ui-permissions-pattern -> origin/spike-ui-permissions-pattern
3452:  * [new branch]            spokanemac-add-passcode-ddm-profile -> origin/spokanemac-add-passcode-ddm-profile
3453:  * [new branch]            spokanemac-dogfood-additional-apps -> origin/spokanemac-dogfood-additional-apps
3454:  * [new branch]            spokanemac-handbook-release-article -> origin/spokanemac-handbook-release-article
3455:  * [new branch]            spokanemac-macdevops-conference-post -> origin/spokanemac-macdevops-conference-post
3456:  * [new branch]            spokanemac-meetup-template  -> origin/spokanemac-meetup-template
3457:  * [new branch]            spokanemac-migrating-fleetdm-from-dogfood-to-terraform-on-aws -> origin/spokanemac-migrating-fleetdm-from-dogfood-to-terraform-on-aws
3458:  * [new branch]            spokanemac-release-article-issue-template -> origin/spokanemac-release-article-issue-template
3459:  * [new branch]            step4-improvements          -> origin/step4-improvements
3460:  * [new branch]            store-query-execution-and-ingestion-errors -> origin/store-query-execution-and-ingestion-errors
3461:  * [new branch]            support-breaking-osquery-downgrades -> origin/support-breaking-osquery-downgrades
...

3528:  * [new branch]            ui-self-service-sg-5-21     -> origin/ui-self-service-sg-5-21
3529:  * [new branch]            unauth-installers           -> origin/unauth-installers
3530:  * [new branch]            uniform-brex-spending-limit -> origin/uniform-brex-spending-limit
3531:  * [new branch]            untx-software               -> origin/untx-software
3532:  * [new branch]            upcoming-activities-mdm-commands -> origin/upcoming-activities-mdm-commands
3533:  * [new branch]            upcoming-commands           -> origin/upcoming-commands
3534:  * [new branch]            update-1password-and-safari-policy-versions-2601200025 -> origin/update-1password-and-safari-policy-versions-2601200025
3535:  * [new branch]            update-1password-and-safari-policy-versions-2601200610 -> origin/update-1password-and-safari-policy-versions-2601200610
3536:  * [new branch]            update-1password-and-safari-policy-versions-2602261818 -> origin/update-1password-and-safari-policy-versions-2602261818
3537:  * [new branch]            update-1password-and-safari-policy-versions-2603101819 -> origin/update-1password-and-safari-policy-versions-2603101819
3538:  * [new branch]            update-1password-and-safari-policy-versions-2604080037 -> origin/update-1password-and-safari-policy-versions-2604080037
3539:  * [new branch]            update-1password-and-safari-policy-versions-2604080637 -> origin/update-1password-and-safari-policy-versions-2604080637
3540:  * [new branch]            update-Cloudflare-WARP-FMA-with-UpgradeCode -> origin/update-Cloudflare-WARP-FMA-with-UpgradeCode
3541:  * [new branch]            update-article-format       -> origin/update-article-format
3542:  * [new branch]            update-bug-report-template  -> origin/update-bug-report-template
3543:  * [new branch]            update-build-script-error-message -> origin/update-build-script-error-message
3544:  * [new branch]            update-cancel-and-done-links -> origin/update-cancel-and-done-links
...

3624:  * [new branch]            update-solutions-folder     -> origin/update-solutions-folder
3625:  * [new branch]            update-testing-qa-apps-2511191525 -> origin/update-testing-qa-apps-2511191525
3626:  * [new branch]            update-tuf-keys             -> origin/update-tuf-keys
3627:  * [new branch]            update-tuf-md               -> origin/update-tuf-md
3628:  * [new branch]            update-usage-statistics-mobile-devices -> origin/update-usage-statistics-mobile-devices
3629:  * [new branch]            update-user-password-length-validation-test -> origin/update-user-password-length-validation-test
3630:  * [new branch]            update-vex-statements       -> origin/update-vex-statements
3631:  * [new branch]            update-why-this-way         -> origin/update-why-this-way
3632:  * [new branch]            upgrade-eslint              -> origin/upgrade-eslint
3633:  * [new branch]            upgrade-go                  -> origin/upgrade-go
3634:  * [new branch]            upgrade-typescript          -> origin/upgrade-typescript
3635:  * [new branch]            upload-msiexec-install-log  -> origin/upload-msiexec-install-log
3636:  * [new branch]            usage-stats-mobile-devices-count -> origin/usage-stats-mobile-devices-count
3637:  * [new branch]            use-dockerhub-creds         -> origin/use-dockerhub-creds
3638:  * [new branch]            use-software-name-helper    -> origin/use-software-name-helper
3639:  * [new branch]            user-channel-error          -> origin/user-channel-error
3640:  * [new branch]            users-table-experiment      -> origin/users-table-experiment
...

3793:  * [new branch]            wesite-fix-sales-rituals    -> origin/wesite-fix-sales-rituals
3794:  * [new branch]            willmayhone88-patch-1       -> origin/willmayhone88-patch-1
3795:  * [new branch]            win-fma-deepl               -> origin/win-fma-deepl
3796:  * [new branch]            win-fma-evernote            -> origin/win-fma-evernote
3797:  * [new branch]            win-fma-goland              -> origin/win-fma-goland
3798:  * [new branch]            win-fma-testing             -> origin/win-fma-testing
3799:  * [new branch]            win-fma-vnc-apps            -> origin/win-fma-vnc-apps
3800:  * [new branch]            win-mdm-loadtest            -> origin/win-mdm-loadtest
3801:  * [new branch]            windows-baseline            -> origin/windows-baseline
3802:  * [new branch]            windows-end-user-experience-labels -> origin/windows-end-user-experience-labels
3803:  * [new branch]            windows-firefox-doh-script  -> origin/windows-firefox-doh-script
3804:  * [new branch]            windows-mdm-autopilot       -> origin/windows-mdm-autopilot
3805:  * [new branch]            windows-mdm-c-poc           -> origin/windows-mdm-c-poc
3806:  * [new branch]            windows-mdm-docs-auto-enroll -> origin/windows-mdm-docs-auto-enroll
3807:  * [new branch]            windows-mdm-load-test       -> origin/windows-mdm-load-test
3808:  * [new branch]            windows_enable_and_configure-error-message -> origin/windows_enable_and_configure-error-message
3809:  * [new branch]            workstations-to-use-edge-channels -> origin/workstations-to-use-edge-channels
...

3821:  * [new branch]            zayhanlon-patch-1           -> origin/zayhanlon-patch-1
3822:  * [new branch]            zenity-execuser             -> origin/zenity-execuser
3823:  * [new branch]            zhumo-patch-1               -> origin/zhumo-patch-1
3824:  * [new branch]            zhumo-patch-2               -> origin/zhumo-patch-2
3825:  * [new branch]            zhumo-patch-3               -> origin/zhumo-patch-3
3826:  * [new branch]            zhumo-patch-4               -> origin/zhumo-patch-4
3827:  * [new branch]            zhumo-patch-5               -> origin/zhumo-patch-5
3828:  * [new branch]            zhumo-patch-6               -> origin/zhumo-patch-6
3829:  * [new branch]            zhumo-patch-7               -> origin/zhumo-patch-7
3830:  * [new branch]            zwass-actions-reminders     -> origin/zwass-actions-reminders
3831:  * [new branch]            zwass-improve-macos-cpe     -> origin/zwass-improve-macos-cpe
3832:  * [new branch]            zwass-patch-1               -> origin/zwass-patch-1
3833:  * [new branch]            zwass-patch-2               -> origin/zwass-patch-2
3834:  * [new branch]            zwinnerman-fixup            -> origin/zwinnerman-fixup
3835:  * [new branch]            zwinnerman-livequery-tracing -> origin/zwinnerman-livequery-tracing
3836:  * [new branch]            zwinnerman-redis-error-handling -> origin/zwinnerman-redis-error-handling
3837:  * [new branch]            zwinnerman-test             -> origin/zwinnerman-test
...

4561:  check-latest: false
4562:  token: ***
4563:  cache: true
4564:  env:
4565:  GH_TOKEN: ***
4566:  LOG_LEVEL: info
4567:  ##[endgroup]
4568:  Setup go version spec 1.26.4
4569:  Found in cache @ /Users/runner/hostedtoolcache/go/1.26.4/arm64
4570:  Added go to the path
4571:  Successfully set up Go version 1.26.4
4572:  [command]/Users/runner/hostedtoolcache/go/1.26.4/arm64/bin/go env GOMODCACHE
4573:  [command]/Users/runner/hostedtoolcache/go/1.26.4/arm64/bin/go env GOCACHE
4574:  /Users/runner/go/pkg/mod
4575:  /Users/runner/Library/Caches/go-build
4576:  ##[warning]Restore cache failed: Dependencies file is not found in /Users/runner/work/fleet/fleet. Supported file pattern: go.mod
4577:  go version go1.26.4 darwin/arm64
...

4768:  - viscosity/darwin
4769:  - visual-studio-code/darwin
4770:  - vnc-server/darwin
4771:  - vyprvpn/darwin
4772:  - whatroute/darwin
4773:  - wifiman/darwin
4774:  - windows-app/darwin
4775:  - wireshark-app/darwin
4776:  - workflowy/darwin
4777:  - xcreds/darwin
4778:  - xquartz/darwin
4779:  - zed/darwin
4780:  - zed/windows
4781:  - zoom-rooms/darwin
4782:  - zoom/darwin
4783:  ##[group]Run # Default to no changes if detection step failed or didn't set output
4784:  �[36;1m# Default to no changes if detection step failed or didn't set output�[0m
4785:  �[36;1mHAS_CHANGES="true"�[0m
...

5107:  go: downloading github.com/getsentry/sentry-go v0.18.0
5108:  go: downloading go.elastic.co/apm/v2 v2.7.0
5109:  go: downloading go.opentelemetry.io/otel v1.43.0
5110:  go: downloading go.opentelemetry.io/otel/metric v1.43.0
5111:  go: downloading go.opentelemetry.io/otel/trace v1.43.0
5112:  go: downloading github.com/mattn/go-colorable v0.1.13
5113:  go: downloading github.com/edsrzf/mmap-go v1.1.0
5114:  go: downloading github.com/secDre4mer/pkcs7 v0.0.0-20240322103146-665324a4461d
5115:  go: downloading github.com/aws/aws-sdk-go-v2/config v1.32.12
5116:  go: downloading github.com/aws/aws-sdk-go-v2 v1.41.5
5117:  go: downloading github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.35.8
5118:  go: downloading github.com/spf13/cobra v1.9.1
5119:  go: downloading github.com/spf13/viper v1.20.1
5120:  go: downloading github.com/go-kit/kit v0.12.0
5121:  go: downloading github.com/smallstep/pkcs7 v0.0.0-20240723090913-5e2c6a136dfa
5122:  go: downloading github.com/hashicorp/go-multierror v1.1.1
5123:  go: downloading github.com/micromdm/plist v0.2.3-0.20260123201933-667adaf87d87
...

5125:  go: downloading github.com/golang-jwt/jwt/v4 v4.5.2
5126:  go: downloading github.com/WatchBeam/clock v0.0.0-20170901150240-b08e6b4da7ea
5127:  go: downloading go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0
5128:  go: downloading golang.org/x/oauth2 v0.35.0
5129:  go: downloading github.com/mattn/go-isatty v0.0.20
5130:  go: downloading github.com/andygrunwald/go-jira v1.16.0
5131:  go: downloading github.com/cenkalti/backoff/v4 v4.3.0
5132:  go: downloading github.com/nukosuke/go-zendesk v0.13.1
5133:  go: downloading github.com/cenkalti/backoff v2.2.1+incompatible
5134:  go: downloading go.opentelemetry.io/otel/sdk v1.43.0
5135:  go: downloading github.com/igm/sockjs-go/v3 v3.0.2
5136:  go: downloading gopkg.in/yaml.v2 v2.4.0
5137:  go: downloading github.com/oschwald/maxminddb-golang v1.10.0
5138:  go: downloading golang.org/x/sys v0.45.0
5139:  go: downloading github.com/elastic/go-sysinfo v1.11.2
5140:  go: downloading github.com/pkg/errors v0.9.1
5141:  go: downloading go.elastic.co/fastjson v1.1.0
...

5197:  go: downloading github.com/groob/finalizer v0.0.0-20170707115354-4c2ed49aabda
5198:  go: downloading cloud.google.com/go/iam v1.5.3
5199:  go: downloading go.opencensus.io v0.24.0
5200:  go: downloading golang.org/x/sync v0.21.0
5201:  go: downloading google.golang.org/grpc v1.79.3
5202:  go: downloading google.golang.org/protobuf v1.36.11
5203:  go: downloading github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8
5204:  go: downloading github.com/nats-io/nkeys v0.4.15
5205:  go: downloading github.com/nats-io/nuid v1.0.1
5206:  go: downloading github.com/micromdm/nanolib v0.2.0
5207:  go: downloading go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352
5208:  go: downloading cloud.google.com/go/auth v0.18.2
5209:  go: downloading cloud.google.com/go/auth/oauth2adapt v0.2.8
5210:  go: downloading cloud.google.com/go/compute/metadata v0.9.0
5211:  go: downloading github.com/google/s2a-go v0.1.9
5212:  go: downloading github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901
5213:  go: downloading cloud.google.com/go/pubsub/v2 v2.0.0
5214:  go: downloading google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7
5215:  go: downloading google.golang.org/genproto v0.0.0-20260128011058-8636f8732409
5216:  go: downloading github.com/googleapis/enterprise-certificate-proxy v0.3.12
5217:  go: downloading google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20
5218:  go: downloading go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0
5219:  time=level=INFO msg="GOOS environment variable is not set. Using system detected: 'darwin'"
5220:  time=level=INFO msg="INSTALLATION_SEARCH_DIRECTORY environment variable is not set. Using default: '/Applications'"
5221:  time=level=INFO msg="Validating app: AdGuard (adguard/darwin)"
5222:  time=level=INFO msg=Downloading...
5223:  time=level=ERROR msg="Error downloading maintained app: downloading installer: reading installer \"https://static.adguard.com/mac/release/AdGuard-2.18.0.2089.dmg\" contents: context deadline exceeded" app=AdGuard
5224:  time=level=INFO msg="Validating app: AdLock (adlock/darwin)"
5225:  time=level=INFO msg=Downloading...
5226:  time=level=INFO msg="Executing install script..." app=AdLock
5227:  time=level=INFO msg="New application detected at: /Applications/AdLock.app" app=AdLock
5228:  time=level=INFO msg="Forcing LaunchServices refresh for: '/Applications/AdLock.app'" app=AdLock
5229:  time=level=INFO msg="Attempting to remove quarantine for: '/Applications/AdLock.app'" app=AdLock
5230:  time=level=INFO msg="Quarantine output error: checking quarantine status: exit status 1" app=AdLock
5231:  time=level=INFO msg="Quarantine status: Quarantine status: 'xattr: /Applications/AdLock.app: No such xattr: com.apple.quarantine'" app=AdLock
5232:  time=level=INFO msg="Spctl output error: <nil>" app=AdLock
5233:  time=level=INFO msg="spctl status: spctl status: '/Applications/AdLock.app: accepted\nsource=Notarized Developer ID'" app=AdLock
5234:  time=level=WARN msg="Error detected in post-installation steps: Error removing app quarantine: adding app to quarantine exceptions: exit status 4. Attempting to continue" app=AdLock
5235:  time=level=INFO msg="Looking for app: AdLock, version: 2.1.7.3" app=AdLock
5236:  time=level=INFO msg="Found app: 'AdLock' at /Applications/AdLock.app, Version: 2.1.7.3, Bundled Version: 2173" app=AdLock
5237:  time=level=INFO msg="Executing uninstall script for app..." app=AdLock
5238:  time=level=INFO msg="Looking for app: AdLock, version: 2.1.7.3" app=AdLock
5239:  time=level=INFO msg="All checks passed for app: AdLock (adlock/darwin)"
5240:  time=level=INFO msg="Validating app: Adobe Acrobat Reader (adobe-acrobat-reader/darwin)"
5241:  time=level=INFO msg=Downloading...
5242:  time=level=INFO msg="Executing install script..." app="Adobe Acrobat Reader"
5243:  time=level=INFO msg="New application detected at: /Applications/Adobe Acrobat Reader.app" app="Adobe Acrobat Reader"
5244:  time=level=INFO msg="Forcing LaunchServices refresh for: '/Applications/Adobe Acrobat Reader.app'" app="Adobe Acrobat Reader"
5245:  time=level=INFO msg="Attempting to remove quarantine for: '/Applications/Adobe Acrobat Reader.app'" app="Adobe Acrobat Reader"
5246:  time=level=INFO msg="Quarantine output error: checking quarantine status: exit status 1" app="Adobe Acrobat Reader"
5247:  time=level=INFO msg="Quarantine status: Quarantine status: 'xattr: /Applications/Adobe Acrobat Reader.app: No such xattr: com.apple.quarantine'" app="Adobe Acrobat Reader"
5248:  time=level=INFO msg="Spctl output error: <nil>" app="Adobe Acrobat Reader"
5249:  time=level=INFO msg="spctl status: spctl status: '/Applications/Adobe Acrobat Reader.app: accepted\nsource=Notarized Developer ID'" app="Adobe Acrobat Reader"
5250:  time=level=WARN msg="Error detected in post-installation steps: Error removing app quarantine: adding app to quarantine exceptions: exit status 4. Attempting to continue" app="Adobe Acrobat Reader"
5251:  time=level=INFO msg="Looking for app: Adobe Acrobat Reader, version: 26.001.21662" app="Adobe Acrobat Reader"
5252:  time=level=INFO msg="Found app: 'Acrobat Reader' at /Applications/Adobe Acrobat Reader.app, Version: 26.001.21662, Bundled Version: 26.001.21662" app="Adobe Acrobat Reader"
5253:  time=level=INFO msg="Executing uninstall script for app..." app="Adobe Acrobat Reader"
5254:  time=level=INFO msg="Looking for app: Adobe Acrobat Reader, version: 26.001.21662" app="Adobe Acrobat Reader"
5255:  time=level=INFO msg="All checks passed for app: Adobe Acrobat Reader (adobe-acrobat-reader/darwin)"
5256:  time=level=INFO msg="Validating app: AlDente (aldente/darwin)"
5257:  time=level=INFO msg=Downloading...
5258:  time=level=INFO msg="Executing install script..." app=AlDente
5259:  time=level=INFO msg="New application detected at: /Applications/AlDente.app" app=AlDente
5260:  time=level=INFO msg="Forcing LaunchServices refresh for: '/Applications/AlDente.app'" app=AlDente
5261:  time=level=INFO msg="Attempting to remove quarantine for: '/Applications/AlDente.app'" app=AlDente
5262:  time=level=INFO msg="Quarantine output error: checking quarantine status: exit status 1" app=AlDente
5263:  time=level=INFO msg="Quarantine status: Quarantine status: 'xattr: /Applications/AlDente.app: No such xattr: com.apple.quarantine'" app=AlDente
5264:  time=level=INFO msg="Spctl output error: <nil>" app=AlDente
5265:  time=level=INFO msg="spctl status: spctl status: '/Applications/AlDente.app: accepted\nsource=Notarized Developer ID'" app=AlDente
5266:  time=level=WARN msg="Error detected in post-installation steps: Error removing app quarantine: adding app to quarantine exceptions: exit status 4. Attempting to continue" app=AlDente
5267:  time=level=INFO msg="Looking for app: AlDente, version: 1.37.3" app=AlDente
5268:  time=level=INFO msg="Found app: 'AlDente' at /Applications/AlDente.app, Version: 1.37.3, Bundled Version: 94" app=AlDente
5269:  time=level=INFO msg="Executing uninstall script for app..." app=AlDente
5270:  time=level=INFO msg="Looking for app: AlDente, version: 1.37.3" app=AlDente
5271:  time=level=INFO msg="All checks passed for app: AlDente (aldente/darwin)"
5272:  time=level=INFO msg="Validating app: Amazon WorkSpaces (amazon-workspaces/darwin)"
5273:  time=level=INFO msg=Downloading...
5274:  time=level=INFO msg="Executing install script..." app="Amazon WorkSpaces"
5275:  time=level=INFO msg="New application detected at: /Applications/WorkSpaces.app" app="Amazon WorkSpaces"
5276:  time=level=INFO msg="Forcing LaunchServices refresh for: '/Applications/WorkSpaces.app'" app="Amazon WorkSpaces"
5277:  time=level=INFO msg="Attempting to remove quarantine for: '/Applications/WorkSpaces.app'" app="Amazon WorkSpaces"
5278:  time=level=INFO msg="Quarantine output error: checking quarantine status: exit status 1" app="Amazon WorkSpaces"
5279:  time=level=INFO msg="Quarantine status: Quarantine status: 'xattr: /Applications/WorkSpaces.app: No such xattr: com.apple.quarantine'" app="Amazon WorkSpaces"
5280:  time=level=INFO msg="Spctl output error: <nil>" app="Amazon WorkSpaces"
5281:  time=level=INFO msg="spctl status: spctl status: '/Applications/WorkSpaces.app: accepted\nsource=Notarized Developer ID'" app="Amazon WorkSpaces"
5282:  time=level=WARN msg="Error detected in post-installation steps: Error removing app quarantine: adding app to quarantine exceptions: exit status 4. Attempting to continue" app="Amazon WorkSpaces"
5283:  time=level=INFO msg="Looking for app: Amazon WorkSpaces, version: 5.32.0.6080" app="Amazon WorkSpaces"
5284:  time=level=INFO msg="Found app: 'Autoupdate' at /Applications/WorkSpaces.app/Contents/Frameworks/Sparkle.framework/Versions/A/Resources/Autoupdate.app, Version: 1.18.1 21-g558cfd21, Bundled Version: 1.18.1" app="Amazon WorkSpaces"
5285:  time=level=INFO msg="Found app: 'WorkSpaces' at /Applications/WorkSpaces.app, Version: 5.32.0.6080, Bundled Version: 5.32.0.6080" app="Amazon WorkSpaces"
5286:  time=level=INFO msg="Executing uninstall script for app..." app="Amazon WorkSpaces"
5287:  time=level=INFO msg="Looking for app: Amazon WorkSpaces, version: 5.32.0.6080" app="Amazon WorkSpaces"
5288:  time=level=INFO msg="All checks passed for app: Amazon WorkSpaces (amazon-workspaces/darwin)"
5289:  time=level=INFO msg="Validating app: Anka (anka-virtualization/darwin)"
5290:  time=level=INFO msg=Downloading...
5291:  time=level=INFO msg="Executing install script..." app=Anka
5292:  time=level=INFO msg="New application detected at: /Applications/Anka.app" app=Anka
5293:  time=level=INFO msg="Forcing LaunchServices refresh for: '/Applications/Anka.app'" app=Anka
5294:  time=level=INFO msg="Attempting to remove quarantine for: '/Applications/Anka.app'" app=Anka
5295:  time=level=INFO msg="Quarantine output error: checking quarantine status: exit status 1" app=Anka
5296:  time=level=INFO msg="Quarantine status: Quarantine status: 'xattr: /Applications/Anka.app: No such xattr: com.apple.quarantine'" app=Anka
5297:  time=level=INFO msg="Spctl output error: <nil>" app=Anka
5298:  time=level=INFO msg="spctl status: spctl status: '/Applications/Anka.app: accepted\nsource=Notarized Developer ID'" app=Anka
5299:  time=level=WARN msg="Error detected in post-installation steps: Error removing app quarantine: adding app to quarantine exceptions: exit status 4. Attempting to continue" app=Anka
5300:  time=level=INFO msg="Looking for app: Anka, version: 3.9.1" app=Anka
5301:  time=level=INFO msg="Found app: 'Anka' at /Applications/Anka.app, Version: 3.9.1, Bundled Version: 216" app=Anka
5302:  time=level=INFO msg="Executing uninstall script for app..." app=Anka
5303:  time=level=INFO msg="Looking for app: Anka, version: 3.9.1" app=Anka
5304:  time=level=INFO msg="All checks passed for app: Anka (anka-virtualization/darwin)"
5305:  time=level=INFO msg="Validating app: AppCleaner (appcleaner/darwin)"
5306:  time=level=INFO msg=Downloading...
5307:  time=level=INFO msg="Executing install script..." app=AppCleaner
5308:  time=level=INFO msg="New application detected at: /Applications/AppCleaner.app" app=AppCleaner
5309:  time=level=INFO msg="Forcing LaunchServices refresh for: '/Applications/AppCleaner.app'" app=AppCleaner
5310:  time=level=INFO msg="Attempting to remove quarantine for: '/Applications/AppCleaner.app'" app=AppCleaner
5311:  time=level=INFO msg="Quarantine output error: checking quarantine status: exit status 1" app=AppCleaner
5312:  time=level=INFO msg="Quarantine status: Quarantine status: 'xattr: /Applications/AppCleaner.app: No such xattr: com.apple.quarantine'" app=AppCleaner
5313:  time=level=INFO msg="Spctl output error: <nil>" app=AppCleaner
5314:  time=level=INFO msg="spctl status: spctl status: '/Applications/AppCleaner.app: accepted\nsource=Notarized Developer ID'" app=AppCleaner
5315:  time=level=WARN msg="Error detected in post-installation steps: Error removing app quarantine: adding app to quarantine exceptions: exit status 4. Attempting to continue" app=AppCleaner
5316:  time=level=INFO msg="Looking for app: AppCleaner, version: 3.6.8" app=AppCleaner
5317:  time=level=INFO msg="Found app: 'AppCleaner' at /Applications/AppCleaner.app, Version: 3.6.8, Bundled Version: 4332" app=AppCleaner
5318:  time=level=INFO msg="Executing uninstall script for app..." app=AppCleaner
5319:  time=level=INFO msg="Looking for app: AppCleaner, version: 3.6.8" app=AppCleaner
5320:  time=level=INFO msg="All checks passed for app: AppCleaner (appcleaner/darwin)"
5321:  time=level=INFO msg="Validating app: Aviatrix VPN Client (aviatrix-vpn-client/darwin)"
5322:  time=level=INFO msg=Downloading...
5323:  time=level=INFO msg="Executing install script..." app="Aviatrix VPN Client"
5324:  time=level=INFO msg="New application detected at: /Applications/Aviatrix VPN Client.app" app="Aviatrix VPN Client"
5325:  time=level=INFO msg="Forcing LaunchServices refresh for: '/Applications/Aviatrix VPN Client.app'" app="Aviatrix VPN Client"
5326:  time=level=INFO msg="Attempting to remove quarantine for: '/Applications/Aviatrix VPN Client.app'" app="Aviatrix VPN Client"
5327:  time=level=INFO msg="Quarantine output error: checking quarantine status: exit status 1" app="Aviatrix VPN Client"
5328:  time=level=INFO msg="Quarantine status: Quarantine status: 'xattr: /Applications/Aviatrix VPN Client.app: No such xattr: com.apple.quarantine'" app="Aviatrix VPN Client"
5329:  time=level=INFO msg="Spctl output error: <nil>" app="Aviatrix VPN Client"
5330:  time=level=INFO msg="spctl status: spctl status: '/Applications/Aviatrix VPN Client.app: accepted\nsource=Notarized Developer ID'" app="Aviatrix VPN Client"
5331:  time=level=WARN msg="Error detected in post-installation steps: Error removing app quarantine: adding app to quarantine exceptions: exit status 4. Attempting to continue" app="Aviatrix VPN Client"
5332:  time=level=INFO msg="Looking for app: Aviatrix VPN Client, version: 2.17.7" app="Aviatrix VPN Client"
5333:  time=level=INFO msg="Found app: 'Aviatrix VPN Client' at /Applications/Aviatrix VPN Client.app, Version: 2.17.7, Bundled Version: 0.0.0" app="Aviatrix VPN Client"
5334:  time=level=INFO msg="Executing uninstall script for app..." app="Aviatrix VPN Client"
5335:  time=level=INFO msg="Looking for app: Aviatrix VPN Client, version: 2.17.7" app="Aviatrix VPN Client"
5336:  time=level=INFO msg="All checks passed for app: Aviatrix VPN Client (aviatrix-vpn-client/darwin)"
5337:  time=level=INFO msg="Validating app: AWS Client VPN (aws-vpn-client/darwin)"
5338:  time=level=INFO msg=Downloading...
5339:  time=level=INFO msg="Executing install script..." app="AWS Client VPN"
5340:  time=level=INFO msg="New application detected at: /Applications/AWS VPN Client" app="AWS Client VPN"
5341:  time=level=INFO msg="Forcing LaunchServices refresh for: '/Applications/AWS VPN Client'" app="AWS Client VPN"
5342:  time=level=WARN msg="Error detected in post-installation steps: Error forcing LaunchServices refresh: forcing LaunchServices refresh: exit status 1. Attempting to continue" app="AWS Client VPN"
5343:  time=level=INFO msg="Looking for app: AWS Client VPN, version: 5.4.0" app="AWS Client VPN"
5344:  time=level=INFO msg="Found app: 'AWS VPN Client' at /Applications/AWS VPN Client/AWS VPN Client.app, Version: 5.4.0, Bundled Version: 5.4.0" app="AWS Client VPN"
5345:  time=level=INFO msg="Executing uninstall script for app..." app="AWS Client VPN"
5346:  time=level=INFO msg="Looking for app: AWS Client VPN, version: 5.4.0" app="AWS Client VPN"
5347:  time=level=INFO msg="All checks passed for app: AWS Client VPN (aws-vpn-client/darwin)"
5348:  time=level=INFO msg="Validating app: Background Music (background-music/darwin)"
5349:  time=level=INFO msg=Downloading...
5350:  time=level=INFO msg="Executing install script..." app="Background Music"
5351:  time=level=INFO msg="New application detected at: /Applications/Background Music.app" app="Background Music"
5352:  time=level=INFO msg="Forcing LaunchServices refresh for: '/Applications/Background Music.app'" app="Background Music"
5353:  time=level=INFO msg="Attempting to remove quarantine for: '/Applications/Background Music.app'" app="Background Music"
5354:  time=level=INFO msg="Quarantine output error: checking quarantine status: exit status 1" app="Background Music"
5355:  time=level=INFO msg="Quarantine status: Quarantine status: 'xattr: /Applications/Background Music.app: No such xattr: com.apple.quarantine'" app="Background Music"
5356:  time=level=INFO msg="Spctl output error: <nil>" app="Background Music"
5357:  time=level=INFO msg="spctl status: spctl status: '/Applications/Background Music.app: accepted\nsource=Notarized Developer ID'" app="Background Music"
5358:  time=level=WARN msg="Error detected in post-installation steps: Error removing app quarantine: adding app to quarantine exceptions: exit status 4. Attempting to continue" app="Background Music"
5359:  time=level=INFO msg="Looking for app: Background Music, version: 0.5.0" app="Background Music"
5360:  time=level=INFO msg="Found app: 'Background Music' at /Applications/Background Music.app, Version: 0.5.0, Bundled Version: 1.0.0" app="Background Music"
5361:  time=level=INFO msg="Executing uninstall script for app..." app="Background Music"
5362:  time=level=INFO msg="Looking for app: Background Music, version: 0.5.0" app="Background Music"
5363:  time=level=INFO msg="All checks passed for app: Background Music (background-music/darwin)"
5364:  time=level=INFO msg="Validating app: Bartender (bartender/darwin)"
5365:  time=level=INFO msg=Downloading...
5366:  time=level=INFO msg="Executing install script..." app=Bartender
5367:  time=level=INFO msg="New application detected at: /Applications/Bartender 6.app" app=Bartender
5368:  time=level=INFO msg="Forcing LaunchServices refresh for: '/Applications/Bartender 6.app'" app=Bartender
5369:  time=level=INFO msg="Attempting to remove quarantine for: '/Applications/Bartender 6.app'" app=Bartender
5370:  time=level=INFO msg="Quarantine output error: checking quarantine status: exit status 1" app=Bartender
5371:  time=level=INFO msg="Quarantine status: Quarantine status: 'xattr: /Applications/Bartender 6.app: No such xattr: com.apple.quarantine'" app=Bartender
5372:  time=level=INFO msg="Spctl output error: <nil>" app=Bartender
5373:  time=level=INFO msg="spctl status: spctl status: '/Applications/Bartender 6.app: accepted\nsource=Notarized Developer ID'" app=Bartender
5374:  time=level=WARN msg="Error detected in post-installation steps: Error removing app quarantine: adding app to quarantine exceptions: exit status 4. Attempting to continue" app=Bartender
5375:  time=level=INFO msg="Looking for app: Bartender, version: 6.5.2" app=Bartender
5376:  time=...

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Closing in favor of #48604.

@github-actions github-actions Bot closed this Jul 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants