Skip to content

Update Fleet-maintained apps - #49191

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

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

Conversation

@fleet-release

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

Copy link
Copy Markdown
Contributor

Automated ingestion of latest Fleet-maintained app data.

Summary by CodeRabbit

  • New Features
    • Added availability for the latest releases of numerous macOS and Windows applications, including Apidog, ChatGPT, Claude, CMake, DataGrip, Postman, OneDrive, Zed, Wireshark, Webex, and others.
  • Bug Fixes
    • Updated installer download links and checksums to ensure reliable installation and verification.
    • Improved cleanup behavior for ChatGPT/Codex, Gemini, and Weasis uninstall processes.
    • Corrected DBeaver Enterprise and Ultimate installer verification checksums.

Generated automatically with cmd/maintained-apps.
@github-actions

Copy link
Copy Markdown
Contributor

Script Diff Results

ee/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 0

ee/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) ===

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Updated 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

  • fleetdm/fleet#49101: Updates overlapping maintained-app entries, including ChatGPT, Claude, and Zed.
  • fleetdm/fleet#48383: Changes the ChatGPT uninstall script reference and embedded script.
  • fleetdm/fleet#48385: Changes the Google Gemini uninstall script reference and embedded script.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive The description is a single generic sentence and does not follow the repository's required template sections or checklist. Add the required template sections, including a Related issue line and the checklist/testing/database items, with any applicable boxes completed.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title matches the main change: updating Fleet-maintained app data, and is concise enough for history scanning.
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-2607130901

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: 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

📥 Commits

Reviewing files that changed from the base of the PR and between 2299f02 and 108602f.

📒 Files selected for processing (41)
  • ee/maintained-apps/outputs/apidog/darwin.json
  • ee/maintained-apps/outputs/chatgpt/darwin.json
  • ee/maintained-apps/outputs/claude/darwin.json
  • ee/maintained-apps/outputs/cmake-app/windows.json
  • ee/maintained-apps/outputs/codexbar/darwin.json
  • ee/maintained-apps/outputs/companion/darwin.json
  • ee/maintained-apps/outputs/coteditor/darwin.json
  • ee/maintained-apps/outputs/datagrip/darwin.json
  • ee/maintained-apps/outputs/datagrip/windows.json
  • ee/maintained-apps/outputs/dbeaver-enterprise/windows.json
  • ee/maintained-apps/outputs/dbeaverultimate/windows.json
  • ee/maintained-apps/outputs/dot/darwin.json
  • ee/maintained-apps/outputs/downie/darwin.json
  • ee/maintained-apps/outputs/fastmail/darwin.json
  • ee/maintained-apps/outputs/google-gemini/darwin.json
  • ee/maintained-apps/outputs/hive-app/darwin.json
  • ee/maintained-apps/outputs/jitsi-meet/darwin.json
  • ee/maintained-apps/outputs/linearmouse/darwin.json
  • ee/maintained-apps/outputs/lookaway/darwin.json
  • ee/maintained-apps/outputs/netron/darwin.json
  • ee/maintained-apps/outputs/nordvpn/windows.json
  • ee/maintained-apps/outputs/notepadexe/darwin.json
  • ee/maintained-apps/outputs/ocenaudio/windows.json
  • ee/maintained-apps/outputs/onedrive/darwin.json
  • ee/maintained-apps/outputs/onedrive/windows.json
  • ee/maintained-apps/outputs/only-switch/darwin.json
  • ee/maintained-apps/outputs/postman/darwin.json
  • ee/maintained-apps/outputs/postman/windows.json
  • ee/maintained-apps/outputs/pritunl/darwin.json
  • ee/maintained-apps/outputs/rewritebar/darwin.json
  • ee/maintained-apps/outputs/stats/darwin.json
  • ee/maintained-apps/outputs/syncthing-app/darwin.json
  • ee/maintained-apps/outputs/transmission/windows.json
  • ee/maintained-apps/outputs/unity-hub/darwin.json
  • ee/maintained-apps/outputs/weasis/windows.json
  • ee/maintained-apps/outputs/webex/windows.json
  • ee/maintained-apps/outputs/whatroute/darwin.json
  • ee/maintained-apps/outputs/wireshark/windows.json
  • ee/maintained-apps/outputs/wispr-flow/darwin.json
  • ee/maintained-apps/outputs/zed/darwin.json
  • ee/maintained-apps/outputs/zed/windows.json

Comment on lines +4 to 16
"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"
]
}

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

🧩 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 -S

Repository: 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' ee

Repository: 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.

Comment on lines 18 to +20
"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"

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

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:

  1. Wrong bundle ID: quit_application 'com.openai.codex' quits Codex, not ChatGPT. The manifest's exists query and install script both target com.openai.chat. ChatGPT will remain running during rm -rf "$APPDIR/ChatGPT.app", which can cause incomplete removal or locked-file errors.

  2. Broken ~/.codex removal: sudo rmdir '~/.codex' — single quotes prevent tilde expansion, so it attempts to remove a literal directory named ~/.codex relative to CWD. Even unquoted, ~ would expand to root's home (script runs as root). Additionally, rmdir only succeeds on empty directories. Use the trash function (which handles ~ expansion via $LOGGED_IN_USER) or sudo rm -rf "/Users/$LOGGED_IN_USER/.codex".

  3. Missing ChatGPT cleanup paths: The script trashes com.openai.codex Library paths but doesn't clean up com.openai.chat support 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.

Suggested change
"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",

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 | 🟡 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.

Suggested change
"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.

@github-actions

Copy link
Copy Markdown
Contributor

Closing in favor of #49196.

@github-actions github-actions Bot closed this Jul 13, 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