Update Fleet-maintained apps - #49191
Conversation
Generated automatically with cmd/maintained-apps.
Script Diff Resultsee/maintained-apps/outputs/apidog/darwin.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/chatgpt/darwin.json=== Install Script (no changes) ===
=== Uninstall // 34cffa61 -> 8a4a4a85 ===
--- /tmp/old.PtjCex 2026-07-13 09:29:45.920189459 +0000
+++ /tmp/new.EOAwMX 2026-07-13 09:29:45.920189459 +0000
@@ -92,19 +92,14 @@
fi
}
-quit_application 'com.openai.chat'
+quit_application 'com.openai.codex'
sudo rm -rf "$APPDIR/ChatGPT.app"
-trash $LOGGED_IN_USER '~/Library/Application Scripts/com.openai.chat.Widgets'
-trash $LOGGED_IN_USER '~/Library/Application Scripts/group.com.openai.chat'
-trash $LOGGED_IN_USER '~/Library/Application Support/ChatGPT'
-trash $LOGGED_IN_USER '~/Library/Application Support/com.openai.chat'
-trash $LOGGED_IN_USER '~/Library/Caches/com.openai.chat'
-trash $LOGGED_IN_USER '~/Library/Containers/com.openai.chat.Widgets'
-trash $LOGGED_IN_USER '~/Library/Group Containers/group.com.openai.chat'
-trash $LOGGED_IN_USER '~/Library/HTTPStorages/ChatGPTHelper.binarycookies'
-trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.openai.chat'
-trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.openai.chat.binarycookies'
-trash $LOGGED_IN_USER '~/Library/Preferences/com.openai.chat.*.plist'
-trash $LOGGED_IN_USER '~/Library/Preferences/com.openai.chat.plist'
-trash $LOGGED_IN_USER '~/Library/Saved Application State/com.openai.chat.savedState'
-trash $LOGGED_IN_USER '~/Library/WebKit/com.openai.chat'
+sudo rmdir '~/.codex'
+trash $LOGGED_IN_USER '~/Library/Application Support/Codex'
+trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.openai.codex.sfl*'
+trash $LOGGED_IN_USER '~/Library/Caches/com.openai.codex'
+trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.openai.codex'
+trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.openai.codex.binarycookies'
+trash $LOGGED_IN_USER '~/Library/Logs/com.openai.codex'
+trash $LOGGED_IN_USER '~/Library/Preferences/com.openai.codex.plist'
+trash $LOGGED_IN_USER '~/Library/Saved Application State/com.openai.codex.savedState'ee/maintained-apps/outputs/claude/darwin.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/cmake-app/windows.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/codexbar/darwin.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/companion/darwin.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/coteditor/darwin.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/datagrip/darwin.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/datagrip/windows.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/dbeaver-enterprise/windows.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/dbeaverultimate/windows.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/dot/darwin.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/downie/darwin.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/fastmail/darwin.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/google-gemini/darwin.json=== Install Script (no changes) ===
=== Uninstall // 8fa0e2d9 -> b919e16a ===
--- /tmp/old.39f58V 2026-07-13 09:29:46.448185822 +0000
+++ /tmp/new.tUgdJ5 2026-07-13 09:29:46.448185822 +0000
@@ -245,5 +245,6 @@
trash $LOGGED_IN_USER '~/Library/Caches/com.google.GeminiMacOS'
trash $LOGGED_IN_USER '~/Library/Caches/com.google.GeminiMacOS.launcher'
trash $LOGGED_IN_USER '~/Library/Google/GoogleSoftwareUpdate/Actives/com.google.GeminiMacOS'
+trash $LOGGED_IN_USER '~/Library/Group Containers/group.com.google.gemini'
trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.google.GeminiMacOS'
trash $LOGGED_IN_USER '~/Library/Preferences/com.google.GeminiMacOS.*'ee/maintained-apps/outputs/hive-app/darwin.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/jitsi-meet/darwin.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/linearmouse/darwin.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/lookaway/darwin.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/netron/darwin.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/nordvpn/windows.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/notepadexe/darwin.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/ocenaudio/windows.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/onedrive/darwin.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/onedrive/windows.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/only-switch/darwin.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/postman/darwin.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/postman/windows.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/pritunl/darwin.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/rewritebar/darwin.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/stats/darwin.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/syncthing-app/darwin.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/transmission/windows.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/unity-hub/darwin.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/weasis/windows.json=== Install Script (no changes) ===
=== Uninstall // 4f269539 -> 0130a935 ===
--- /tmp/old.v7LKkz 2026-07-13 09:29:47.196180667 +0000
+++ /tmp/new.bXLKWJ 2026-07-13 09:29:47.196180667 +0000
@@ -1,26 +1,28 @@
-$product_code = '{13FA431D-E37A-3CF7-9BA6-3066FCC66CAA}'
-$timeoutSeconds = 300 # 5 minute timeout
-
-# Fleet uninstalls app using product code that's extracted on upload
-$process = Start-Process msiexec -ArgumentList @("/quiet", "/x", $product_code, "/norestart") -PassThru
-
-# Wait for process with timeout
-$completed = $process.WaitForExit($timeoutSeconds * 1000)
-
-if (-not $completed) {
- Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue
- Exit 1603 # ERROR_UNINSTALL_FAILURE
-}
+# Fleet uninstalls app by finding all related product codes for the specified upgrade code
+$inst = New-Object -ComObject "WindowsInstaller.Installer"
+$timeoutSeconds = 300 # 5 minute timeout per product
# MSI exit codes that indicate success. 3010 = ERROR_SUCCESS_REBOOT_REQUIRED,
# 1641 = ERROR_SUCCESS_REBOOT_INITIATED. Treat these as success rather than failure.
$successCodes = @(0, 3010, 1641)
-# Check exit code and output result
-if ($successCodes -contains $process.ExitCode) {
- Write-Output "Exit 0"
- Exit 0
-} else {
- Write-Output "Exit $($process.ExitCode)"
- Exit $process.ExitCode
+foreach ($product_code in $inst.RelatedProducts('{3AEDC24E-48A8-4623-AB39-0C3C01C7383A}')) {
+ $process = Start-Process msiexec -ArgumentList @("/quiet", "/x", $product_code, "/norestart") -PassThru
+
+ # Wait for process with timeout
+ $completed = $process.WaitForExit($timeoutSeconds * 1000)
+
+ if (-not $completed) {
+ Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue
+ Exit 1603 # ERROR_UNINSTALL_FAILURE
+ }
+
+ # If the uninstall failed, bail
+ if ($successCodes -notcontains $process.ExitCode) {
+ Write-Output "Uninstall for $($product_code) exited $($process.ExitCode)"
+ Exit $process.ExitCode
+ }
}
+
+# All uninstalls succeeded; exit success
+Exit 0ee/maintained-apps/outputs/webex/windows.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/whatroute/darwin.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/wireshark/windows.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/wispr-flow/darwin.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/zed/darwin.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/zed/windows.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) === |
WalkthroughUpdated maintained-app manifests for numerous macOS and Windows applications to newer versions and installer artifacts. Changes include synchronized version checks, download URLs, and SHA-256 checksums. ChatGPT, Gemini, and Weasis also received updated uninstall script references and embedded uninstall behavior. Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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/chatgpt/darwin.json`:
- Around line 18-20: Update the uninstall script’s quit_application call to
target com.openai.chat, and replace the quoted sudo rmdir '~/.codex' with
removal through trash or an explicit /Users/$LOGGED_IN_USER path. In the cleanup
sequence after ChatGPT.app removal, use ChatGPT’s com.openai.chat identifiers
for support-file paths, adding all required ChatGPT artifacts while preserving
Codex cleanup only if both applications are intended to be cleaned.
- Around line 4-16: The ChatGPT macOS package uses a ZIP installer while the
referenced install script expects a disk image. Update the installer metadata
around installer_url and install_script_ref so both use compatible formats:
either switch installer_url back to a DMG or update install_script_ref 61140545
to extract and install the ZIP payload.
In `@ee/maintained-apps/outputs/google-gemini/darwin.json`:
- Line 19: Update the actual files-removal pipeline in remove_receipt_files to
prefix paths with FULL_INSTALL_LOCATION, matching its echo log and the
directory-removal pipeline; leave the existing pkgutil, null-delimited xargs,
and other behavior unchanged.
🪄 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: de27d439-f68e-4d3d-ba7c-b1d820410c20
📒 Files selected for processing (41)
ee/maintained-apps/outputs/apidog/darwin.jsonee/maintained-apps/outputs/chatgpt/darwin.jsonee/maintained-apps/outputs/claude/darwin.jsonee/maintained-apps/outputs/cmake-app/windows.jsonee/maintained-apps/outputs/codexbar/darwin.jsonee/maintained-apps/outputs/companion/darwin.jsonee/maintained-apps/outputs/coteditor/darwin.jsonee/maintained-apps/outputs/datagrip/darwin.jsonee/maintained-apps/outputs/datagrip/windows.jsonee/maintained-apps/outputs/dbeaver-enterprise/windows.jsonee/maintained-apps/outputs/dbeaverultimate/windows.jsonee/maintained-apps/outputs/dot/darwin.jsonee/maintained-apps/outputs/downie/darwin.jsonee/maintained-apps/outputs/fastmail/darwin.jsonee/maintained-apps/outputs/google-gemini/darwin.jsonee/maintained-apps/outputs/hive-app/darwin.jsonee/maintained-apps/outputs/jitsi-meet/darwin.jsonee/maintained-apps/outputs/linearmouse/darwin.jsonee/maintained-apps/outputs/lookaway/darwin.jsonee/maintained-apps/outputs/netron/darwin.jsonee/maintained-apps/outputs/nordvpn/windows.jsonee/maintained-apps/outputs/notepadexe/darwin.jsonee/maintained-apps/outputs/ocenaudio/windows.jsonee/maintained-apps/outputs/onedrive/darwin.jsonee/maintained-apps/outputs/onedrive/windows.jsonee/maintained-apps/outputs/only-switch/darwin.jsonee/maintained-apps/outputs/postman/darwin.jsonee/maintained-apps/outputs/postman/windows.jsonee/maintained-apps/outputs/pritunl/darwin.jsonee/maintained-apps/outputs/rewritebar/darwin.jsonee/maintained-apps/outputs/stats/darwin.jsonee/maintained-apps/outputs/syncthing-app/darwin.jsonee/maintained-apps/outputs/transmission/windows.jsonee/maintained-apps/outputs/unity-hub/darwin.jsonee/maintained-apps/outputs/weasis/windows.jsonee/maintained-apps/outputs/webex/windows.jsonee/maintained-apps/outputs/whatroute/darwin.jsonee/maintained-apps/outputs/wireshark/windows.jsonee/maintained-apps/outputs/wispr-flow/darwin.jsonee/maintained-apps/outputs/zed/darwin.jsonee/maintained-apps/outputs/zed/windows.json
| "version": "26.707.61608", | ||
| "queries": { | ||
| "exists": "SELECT 1 FROM apps WHERE bundle_identifier = 'com.openai.chat';", | ||
| "patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps WHERE bundle_identifier = 'com.openai.chat' AND version_compare(bundle_short_version, '1.2026.183') < 0);" | ||
| "patched": "SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM apps WHERE bundle_identifier = 'com.openai.chat' AND version_compare(bundle_short_version, '26.707.61608') < 0);" | ||
| }, | ||
| "installer_url": "https://persistent.oaistatic.com/sidekick/public/ChatGPT_Desktop_public_1.2026.183_1783607847.dmg", | ||
| "installer_url": "https://persistent.oaistatic.com/codex-app-prod/ChatGPT-darwin-arm64-26.707.61608.zip", | ||
| "install_script_ref": "61140545", | ||
| "uninstall_script_ref": "34cffa61", | ||
| "sha256": "49b33cadd2ec659b76352384f7ebd332a7ec7029663365a9f720f4a251d3b8d1", | ||
| "uninstall_script_ref": "8a4a4a85", | ||
| "sha256": "83ece86053eea4091f3b624766e21b584b4039656e3b00e4bdea9d868c109ad0", | ||
| "default_categories": [ | ||
| "Productivity" | ||
| ] | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check the previous installer_url from git history
git log -1 --format=%H -- ee/maintained-apps/outputs/chatgpt/darwin.json
git show HEAD~1:ee/maintained-apps/outputs/chatgpt/darwin.json 2>/dev/null | jq -r '.versions[0].installer_url'Repository: fleetdm/fleet
Length of output: 290
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== current file ==\n'
cat -n ee/maintained-apps/outputs/chatgpt/darwin.json | sed -n '1,80p'
printf '\n== locate install script ref ==\n'
rg -n '"61140545"|61140545|install_script_ref|hdiutil attach|installer_url' ee -S
printf '\n== candidate script files mentioning hdiutil attach ==\n'
rg -n 'hdiutil attach' ee -SRepository: fleetdm/fleet
Length of output: 50371
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '== chatgpt darwin.json entry =='
jq -r '
.versions[]
| select(.install_script_ref == "61140545")
| {version, installer_url, install_script_ref, uninstall_script_ref}
' ee/maintained-apps/outputs/chatgpt/darwin.json
echo
echo '== install script 61140545 =='
jq -r '
.versions[]
| select(.install_script_ref == "61140545")
| .install_script
' ee/maintained-apps/outputs/chatgpt/darwin.json | cat -n
echo
echo '== previous installer_url =='
git show HEAD~1:ee/maintained-apps/outputs/chatgpt/darwin.json 2>/dev/null | jq -r '.versions[] | select(.install_script_ref == "61140545") | .installer_url'Repository: fleetdm/fleet
Length of output: 568
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '== file shape =='
python3 - <<'PY'
import json
p='ee/maintained-apps/outputs/chatgpt/darwin.json'
with open(p) as f:
data=json.load(f)
print(data.keys())
print('versions:', len(data.get('versions', [])))
for i,v in enumerate(data.get('versions', [])):
print(i, v.keys())
PY
echo
echo '== search for install script ref and hdiutil attach =='
rg -n '61140545|hdiutil attach|ChatGPT-darwin-arm64-26.707.61608.zip|sidekick/public/ChatGPT_Desktop_public_1.2026.183_1783607847.dmg' ee -S
echo
echo '== locate chatgpt-related script files =='
fd -a 'chatgpt|ChatGPT' eeRepository: fleetdm/fleet
Length of output: 50371
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '== chatgpt refs =='
jq -r '
.refs | keys[] as $k
| select($k == "61140545" or $k == "8a4a4a85")
| "\($k):\n\(.[$k])\n"
' ee/maintained-apps/outputs/chatgpt/darwin.json
echo
echo '== search for INSTALLER_PATH preprocessing / zip handling =='
rg -n 'INSTALLER_PATH|zip|unzip|ditto -x|hdiutil attach' ee/maintained-apps ee -S --glob '!**/node_modules/**' --glob '!**/dist/**' | sed -n '1,220p'
echo
echo '== chatgpt-specific script references =='
rg -n 'chatgpt|ChatGPT|61140545|8a4a4a85' ee/maintained-apps -S | sed -n '1,220p'Repository: fleetdm/fleet
Length of output: 50370
ZIP installer is incompatible with the current install script
installer_url now points to a .zip, but install_script_ref still runs hdiutil attach "$INSTALLER_PATH". That only works for DMG/ISO images, so this install will fail unless the script is updated to unzip the payload or the URL is switched back to a DMG.
🤖 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/chatgpt/darwin.json` around lines 4 - 16, The
ChatGPT macOS package uses a ZIP installer while the referenced install script
expects a disk image. Update the installer metadata around installer_url and
install_script_ref so both use compatible formats: either switch installer_url
back to a DMG or update install_script_ref 61140545 to extract and install the
ZIP payload.
| "refs": { | ||
| "34cffa61": "#!/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\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\nquit_application 'com.openai.chat'\nsudo rm -rf \"$APPDIR/ChatGPT.app\"\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/com.openai.chat.Widgets'\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/group.com.openai.chat'\ntrash $LOGGED_IN_USER '~/Library/Application Support/ChatGPT'\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.openai.chat'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.openai.chat'\ntrash $LOGGED_IN_USER '~/Library/Containers/com.openai.chat.Widgets'\ntrash $LOGGED_IN_USER '~/Library/Group Containers/group.com.openai.chat'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/ChatGPTHelper.binarycookies'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.openai.chat'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.openai.chat.binarycookies'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.openai.chat.*.plist'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.openai.chat.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/com.openai.chat.savedState'\ntrash $LOGGED_IN_USER '~/Library/WebKit/com.openai.chat'\n", | ||
| "61140545": "#!/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.openai.chat'\nif [ -d \"$APPDIR/ChatGPT.app\" ]; then\n\tsudo mv \"$APPDIR/ChatGPT.app\" \"$TMPDIR/ChatGPT.app.bkp\"\nfi\nsudo cp -R \"$TMPDIR/ChatGPT.app\" \"$APPDIR\"\nrelaunch_application 'com.openai.chat'\n" | ||
| "61140545": "#!/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.openai.chat'\nif [ -d \"$APPDIR/ChatGPT.app\" ]; then\n\tsudo mv \"$APPDIR/ChatGPT.app\" \"$TMPDIR/ChatGPT.app.bkp\"\nfi\nsudo cp -R \"$TMPDIR/ChatGPT.app\" \"$APPDIR\"\nrelaunch_application 'com.openai.chat'\n", | ||
| "8a4a4a85": "#!/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\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\nquit_application 'com.openai.codex'\nsudo rm -rf \"$APPDIR/ChatGPT.app\"\nsudo rmdir '~/.codex'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Codex'\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.openai.codex.sfl*'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.openai.codex'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.openai.codex'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.openai.codex.binarycookies'\ntrash $LOGGED_IN_USER '~/Library/Logs/com.openai.codex'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.openai.codex.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/com.openai.codex.savedState'\n" |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Uninstall script targets com.openai.codex instead of com.openai.chat — ChatGPT won't be quit or cleaned up.
The new uninstall script (ref 8a4a4a85) has three issues:
-
Wrong bundle ID:
quit_application 'com.openai.codex'quits Codex, not ChatGPT. The manifest'sexistsquery and install script both targetcom.openai.chat. ChatGPT will remain running duringrm -rf "$APPDIR/ChatGPT.app", which can cause incomplete removal or locked-file errors. -
Broken
~/.codexremoval:sudo rmdir '~/.codex'— single quotes prevent tilde expansion, so it attempts to remove a literal directory named~/.codexrelative to CWD. Even unquoted,~would expand to root's home (script runs as root). Additionally,rmdironly succeeds on empty directories. Use thetrashfunction (which handles~expansion via$LOGGED_IN_USER) orsudo rm -rf "/Users/$LOGGED_IN_USER/.codex". -
Missing ChatGPT cleanup paths: The script trashes
com.openai.codexLibrary paths but doesn't clean upcom.openai.chatsupport files (Application Support, Caches, Preferences, Saved Application State, etc.). If the intent is to clean up both ChatGPT and Codex artifacts, both sets of paths should be present.
🔧 Proposed fix for uninstall script
-quit_application 'com.openai.codex'
-sudo rm -rf "$APPDIR/ChatGPT.app"
-sudo rmdir '~/.codex'
-trash $LOGGED_IN_USER '~/Library/Application Support/Codex'
-trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.openai.codex.sfl*'
-trash $LOGGED_IN_USER '~/Library/Caches/com.openai.codex'
-trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.openai.codex'
-trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.openai.codex.binarycookies'
-trash $LOGGED_IN_USER '~/Library/Logs/com.openai.codex'
-trash $LOGGED_IN_USER '~/Library/Preferences/com.openai.codex.plist'
-trash $LOGGED_IN_USER '~/Library/Saved Application State/com.openai.codex.savedState'
+quit_application 'com.openai.chat'
+sudo rm -rf "$APPDIR/ChatGPT.app"
+trash $LOGGED_IN_USER '~/.codex'
+trash $LOGGED_IN_USER '~/Library/Application Support/Codex'
+trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.openai.codex.sfl*'
+trash $LOGGED_IN_USER '~/Library/Caches/com.openai.codex'
+trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.openai.codex'
+trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.openai.codex.binarycookies'
+trash $LOGGED_IN_USER '~/Library/Logs/com.openai.codex'
+trash $LOGGED_IN_USER '~/Library/Preferences/com.openai.codex.plist'
+trash $LOGGED_IN_USER '~/Library/Saved Application State/com.openai.codex.savedState'If ChatGPT-specific paths should also be cleaned, add them as well.
📝 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.
| "refs": { | |
| "34cffa61": "#!/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\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\nquit_application 'com.openai.chat'\nsudo rm -rf \"$APPDIR/ChatGPT.app\"\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/com.openai.chat.Widgets'\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/group.com.openai.chat'\ntrash $LOGGED_IN_USER '~/Library/Application Support/ChatGPT'\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.openai.chat'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.openai.chat'\ntrash $LOGGED_IN_USER '~/Library/Containers/com.openai.chat.Widgets'\ntrash $LOGGED_IN_USER '~/Library/Group Containers/group.com.openai.chat'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/ChatGPTHelper.binarycookies'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.openai.chat'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.openai.chat.binarycookies'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.openai.chat.*.plist'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.openai.chat.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/com.openai.chat.savedState'\ntrash $LOGGED_IN_USER '~/Library/WebKit/com.openai.chat'\n", | |
| "61140545": "#!/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.openai.chat'\nif [ -d \"$APPDIR/ChatGPT.app\" ]; then\n\tsudo mv \"$APPDIR/ChatGPT.app\" \"$TMPDIR/ChatGPT.app.bkp\"\nfi\nsudo cp -R \"$TMPDIR/ChatGPT.app\" \"$APPDIR\"\nrelaunch_application 'com.openai.chat'\n" | |
| "61140545": "#!/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.openai.chat'\nif [ -d \"$APPDIR/ChatGPT.app\" ]; then\n\tsudo mv \"$APPDIR/ChatGPT.app\" \"$TMPDIR/ChatGPT.app.bkp\"\nfi\nsudo cp -R \"$TMPDIR/ChatGPT.app\" \"$APPDIR\"\nrelaunch_application 'com.openai.chat'\n", | |
| "8a4a4a85": "#!/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\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\nquit_application 'com.openai.codex'\nsudo rm -rf \"$APPDIR/ChatGPT.app\"\nsudo rmdir '~/.codex'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Codex'\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.openai.codex.sfl*'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.openai.codex'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.openai.codex'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.openai.codex.binarycookies'\ntrash $LOGGED_IN_USER '~/Library/Logs/com.openai.codex'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.openai.codex.plist'\ntrash $LOGGED_IN_USER '~/Library/Saved Application State/com.openai.codex.savedState'\n" | |
| quit_application 'com.openai.chat' | |
| sudo rm -rf "$APPDIR/ChatGPT.app" | |
| trash $LOGGED_IN_USER '~/.codex' | |
| trash $LOGGED_IN_USER '~/Library/Application Support/Codex' | |
| trash $LOGGED_IN_USER '~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/com.openai.codex.sfl*' | |
| trash $LOGGED_IN_USER '~/Library/Caches/com.openai.codex' | |
| trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.openai.codex' | |
| trash $LOGGED_IN_USER '~/Library/HTTPStorages/com.openai.codex.binarycookies' | |
| trash $LOGGED_IN_USER '~/Library/Logs/com.openai.codex' | |
| trash $LOGGED_IN_USER '~/Library/Preferences/com.openai.codex.plist' | |
| trash $LOGGED_IN_USER '~/Library/Saved Application State/com.openai.codex.savedState' |
🤖 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/chatgpt/darwin.json` around lines 18 - 20, Update
the uninstall script’s quit_application call to target com.openai.chat, and
replace the quoted sudo rmdir '~/.codex' with removal through trash or an
explicit /Users/$LOGGED_IN_USER path. In the cleanup sequence after ChatGPT.app
removal, use ChatGPT’s com.openai.chat identifiers for support-file paths,
adding all required ChatGPT artifacts while preserving Codex cleanup only if
both applications are intended to be cleaned.
| ], | ||
| "refs": { | ||
| "8fa0e2d9": "#!/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\nquit_application 'com.google.GeminiMacOS'\nquit_application 'com.google.GeminiMacOS.launcher'\nsudo rm -rf \"$APPDIR/Gemini.app\"\nremove_launchctl_service 'com.google.GoogleUpdater.wake.system'\nremove_launchctl_service 'com.google.keystone.agent'\nremove_launchctl_service 'com.google.keystone.daemon'\nremove_launchctl_service 'com.google.keystone.system.agent'\nremove_launchctl_service 'com.google.keystone.system.xpcservice'\nremove_launchctl_service 'com.google.keystone.xpcservice'\nremove_pkg_files 'com.google.pkg.Keystone'\nforget_pkg 'com.google.pkg.Keystone'\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.google.GeminiMacOS'\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.google.GeminiMacOS.launcher'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.google.GeminiMacOS'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.google.GeminiMacOS.launcher'\ntrash $LOGGED_IN_USER '~/Library/Google/GoogleSoftwareUpdate/Actives/com.google.GeminiMacOS'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.google.GeminiMacOS'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.google.GeminiMacOS.*'\n", | ||
| "b919e16a": "#!/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\nquit_application 'com.google.GeminiMacOS'\nquit_application 'com.google.GeminiMacOS.launcher'\nsudo rm -rf \"$APPDIR/Gemini.app\"\nremove_launchctl_service 'com.google.GoogleUpdater.wake.system'\nremove_launchctl_service 'com.google.keystone.agent'\nremove_launchctl_service 'com.google.keystone.daemon'\nremove_launchctl_service 'com.google.keystone.system.agent'\nremove_launchctl_service 'com.google.keystone.system.xpcservice'\nremove_launchctl_service 'com.google.keystone.xpcservice'\nremove_pkg_files 'com.google.pkg.Keystone'\nforget_pkg 'com.google.pkg.Keystone'\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.google.GeminiMacOS'\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.google.GeminiMacOS.launcher'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.google.GeminiMacOS'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.google.GeminiMacOS.launcher'\ntrash $LOGGED_IN_USER '~/Library/Google/GoogleSoftwareUpdate/Actives/com.google.GeminiMacOS'\ntrash $LOGGED_IN_USER '~/Library/Group Containers/group.com.google.gemini'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.google.GeminiMacOS'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.google.GeminiMacOS.*'\n", |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
remove_receipt_files echo/actual command use different path variables.
The echo log uses ${FULL_INSTALL_LOCATION}/ (which includes VOLUME), but the actual sed command uses /${INSTALL_LOCATION}/ (ignoring VOLUME). On non-boot volumes the actual command resolves to the wrong path. The dirs command below correctly uses ${FULL_INSTALL_LOCATION}/ — the files command should match.
Proposed fix: use FULL_INSTALL_LOCATION in the actual files command
- 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
+ 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📝 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.
| "b919e16a": "#!/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\nquit_application 'com.google.GeminiMacOS'\nquit_application 'com.google.GeminiMacOS.launcher'\nsudo rm -rf \"$APPDIR/Gemini.app\"\nremove_launchctl_service 'com.google.GoogleUpdater.wake.system'\nremove_launchctl_service 'com.google.keystone.agent'\nremove_launchctl_service 'com.google.keystone.daemon'\nremove_launchctl_service 'com.google.keystone.system.agent'\nremove_launchctl_service 'com.google.keystone.system.xpcservice'\nremove_launchctl_service 'com.google.keystone.xpcservice'\nremove_pkg_files 'com.google.pkg.Keystone'\nforget_pkg 'com.google.pkg.Keystone'\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.google.GeminiMacOS'\ntrash $LOGGED_IN_USER '~/Library/Application Support/com.google.GeminiMacOS.launcher'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.google.GeminiMacOS'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.google.GeminiMacOS.launcher'\ntrash $LOGGED_IN_USER '~/Library/Google/GoogleSoftwareUpdate/Actives/com.google.GeminiMacOS'\ntrash $LOGGED_IN_USER '~/Library/Group Containers/group.com.google.gemini'\ntrash $LOGGED_IN_USER '~/Library/HTTPStorages/com.google.GeminiMacOS'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.google.GeminiMacOS.*'\n", | |
| 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 |
🤖 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/google-gemini/darwin.json` at line 19, Update the
actual files-removal pipeline in remove_receipt_files to prefix paths with
FULL_INSTALL_LOCATION, matching its echo log and the directory-removal pipeline;
leave the existing pkgutil, null-delimited xargs, and other behavior unchanged.
|
Closing in favor of #49196. |
Automated ingestion of latest Fleet-maintained app data.
Summary by CodeRabbit