Defuse Docker Desktop's install-on-quit updater in macOS FMA install script - #50451
Conversation
Adds NVDA (NV Access screen reader) from winget NVAccess.NVDA 2026.1.1. Identity fields were read out of the shipped installer rather than the winget manifest, which is misleading in two ways: - The manifest reports Architecture: x86, but only the NSIS launcher stub is 32-bit. nvda_noUIAccess.exe and nvda_slave.exe are x64, so NVDA registers in the native registry view, not Wow6432Node. - The registry DisplayName is "NVDA <version>", not "NVDA", so the app needs fuzzy_match_name. Publisher is "NV Access". DisplayVersion is the 4-part 2026.1.1.55980 against winget's 2026.1.1. That reconciles without a validator exception via the existing HasPrefix(found, expected+".") branch, and version_compare treats the installed copy as newer, so patch policies won't report a false "update available". The install script can't trust the installer's exit code: NVDA pops a modal message box on every install failure path with no silent guard and then exits 0 anyway, which would hang forever as SYSTEM and report a false success. It uses a watchdog plus an Add/Remove Programs registration poll as the real success signal. On timeout it kills only the launcher's temp children, never nvda.exe, so it cannot cut off a signed-in user's screen reader. installer_scope is "" because the winget manifest declares no Scope, so the ingester derives "" and "machine" panics with "failed to find installer". NVDA does install machine-wide. Install and uninstall switches are vendor-documented: --install-silent and the uninstaller's /S.
…script Quitting Docker Desktop while one of its own self-updates is staged triggers Docker's install-on-quit updater, which renames /Applications/Docker.app to Docker.app.back and moves the staged copy into place, racing the FMA install script. Users installing updates from self-service saw Docker's updater fail with "failed to back up /Applications/Docker.app before update: ... file exists". Remove the whole com.docker.install staging directory (staged bundle plus updater state) before quitting the app so the quit can't trigger the updater, and wait out (bounded, 30s) any updater already in flight before touching /Applications/Docker.app.
Script Diff Resultsee/maintained-apps/outputs/docker-desktop/darwin.json=== Install // de33e1ce -> 4afb9744 ===
--- /tmp/old.4yzpqz 2026-08-03 17:40:43.904999063 +0000
+++ /tmp/new.Qf6dFT 2026-08-03 17:40:43.904999063 +0000
@@ -122,18 +122,22 @@
hdiutil detach "$MOUNT_POINT"
MOUNT_POINT=""
# copy to the applications folder
+# Quitting Docker Desktop with a staged self-update triggers its install-on-quit
+# updater, which renames Docker.app to Docker.app.back and races this script.
+# Remove the staging dir (staged bundle + updater state) first so it can't fire.
+sudo rm -rf /Users/*/Library/"Application Support"/com.docker.install
quit_and_track_application 'com.electron.dockerdesktop'
+# Wait out any updater already in flight before touching /Applications/Docker.app.
+SECONDS=0
+while pgrep -f 'com\.docker\.install' >/dev/null 2>&1 && (( SECONDS < 30 )); do
+ sleep 1
+done
if [ -d "$APPDIR/Docker.app" ]; then
sudo mv "$APPDIR/Docker.app" "$TMPDIR/Docker.app.bkp"
fi
-# Docker Desktop's own in-app updater leaves stale copies of the old app behind:
-# a Docker.app.back bundle alongside Docker.app, and a staged copy at
-# ~/Library/Application Support/com.docker.install/in_progress/Docker.app.
-# osquery's apps table still picks these up by bundle_identifier, which causes
-# Fleet patch policies to report Docker as out of date even after a successful
-# upgrade.
+# Remove stale self-updater leftovers; osquery's apps table picks them up by
+# bundle_identifier and patch policies report Docker as out of date.
sudo rm -rf "$APPDIR/Docker.app.back"
-sudo rm -rf /Users/*/Library/"Application Support"/com.docker.install/in_progress/Docker.app
sudo cp -R "$TMPDIR/Docker.app" "$APPDIR"
relaunch_application 'com.electron.dockerdesktop'
mkdir -p /usr/local/cli-plugins
@@ -145,8 +149,6 @@
/bin/ln -h -f -s -- "$APPDIR/Docker.app/Contents/Resources/bin/docker-credential-desktop" "/usr/local/bin/docker-credential-desktop"
/bin/ln -h -f -s -- "$APPDIR/Docker.app/Contents/Resources/bin/docker-credential-ecr-login" "/usr/local/bin/docker-credential-ecr-login"
/bin/ln -h -f -s -- "$APPDIR/Docker.app/Contents/Resources/bin/docker-credential-osxkeychain" "/usr/local/bin/docker-credential-osxkeychain"
-# A staged self-update can fire during the quit/relaunch window above and
-# recreate the stale copies after the earlier removal, so delete them again
-# now that the new bundle is in place.
+# Remove stale copies recreated during the quit/relaunch window, if any.
sudo rm -rf "$APPDIR/Docker.app.back"
sudo rm -rf /Users/*/Library/"Application Support"/com.docker.install/in_progress/Docker.app
=== Uninstall Script (no changes) ===ee/maintained-apps/outputs/nvda/windows.json=== Install Script (no changes) ===
=== Uninstall Script (no changes) === |
Script Diff Resultsee/maintained-apps/outputs/docker-desktop/darwin.json=== Install // de33e1ce -> 4afb9744 ===
--- /tmp/old.f7JYYL 2026-08-03 17:42:48.703161602 +0000
+++ /tmp/new.V69QHS 2026-08-03 17:42:48.703161602 +0000
@@ -122,18 +122,22 @@
hdiutil detach "$MOUNT_POINT"
MOUNT_POINT=""
# copy to the applications folder
+# Quitting Docker Desktop with a staged self-update triggers its install-on-quit
+# updater, which renames Docker.app to Docker.app.back and races this script.
+# Remove the staging dir (staged bundle + updater state) first so it can't fire.
+sudo rm -rf /Users/*/Library/"Application Support"/com.docker.install
quit_and_track_application 'com.electron.dockerdesktop'
+# Wait out any updater already in flight before touching /Applications/Docker.app.
+SECONDS=0
+while pgrep -f 'com\.docker\.install' >/dev/null 2>&1 && (( SECONDS < 30 )); do
+ sleep 1
+done
if [ -d "$APPDIR/Docker.app" ]; then
sudo mv "$APPDIR/Docker.app" "$TMPDIR/Docker.app.bkp"
fi
-# Docker Desktop's own in-app updater leaves stale copies of the old app behind:
-# a Docker.app.back bundle alongside Docker.app, and a staged copy at
-# ~/Library/Application Support/com.docker.install/in_progress/Docker.app.
-# osquery's apps table still picks these up by bundle_identifier, which causes
-# Fleet patch policies to report Docker as out of date even after a successful
-# upgrade.
+# Remove stale self-updater leftovers; osquery's apps table picks them up by
+# bundle_identifier and patch policies report Docker as out of date.
sudo rm -rf "$APPDIR/Docker.app.back"
-sudo rm -rf /Users/*/Library/"Application Support"/com.docker.install/in_progress/Docker.app
sudo cp -R "$TMPDIR/Docker.app" "$APPDIR"
relaunch_application 'com.electron.dockerdesktop'
mkdir -p /usr/local/cli-plugins
@@ -145,8 +149,6 @@
/bin/ln -h -f -s -- "$APPDIR/Docker.app/Contents/Resources/bin/docker-credential-desktop" "/usr/local/bin/docker-credential-desktop"
/bin/ln -h -f -s -- "$APPDIR/Docker.app/Contents/Resources/bin/docker-credential-ecr-login" "/usr/local/bin/docker-credential-ecr-login"
/bin/ln -h -f -s -- "$APPDIR/Docker.app/Contents/Resources/bin/docker-credential-osxkeychain" "/usr/local/bin/docker-credential-osxkeychain"
-# A staged self-update can fire during the quit/relaunch window above and
-# recreate the stale copies after the earlier removal, so delete them again
-# now that the new bundle is in place.
+# Remove stale copies recreated during the quit/relaunch window, if any.
sudo rm -rf "$APPDIR/Docker.app.back"
sudo rm -rf /Users/*/Library/"Application Support"/com.docker.install/in_progress/Docker.app
=== Uninstall Script (no changes) === |
There was a problem hiding this comment.
Pull request overview
This PR updates Fleet-maintained app assets/scripts to address Docker Desktop macOS self-service update failures caused by Docker’s install-on-quit updater racing the FMA install script, and also introduces a new NVDA (Windows) maintained app plus a corresponding Software page icon.
Changes:
- Update Docker Desktop macOS install script to remove
com.docker.installstaging before quitting and add a bounded wait for any updater process in flight. - Add NVDA as a Windows maintained app (winget input, install/uninstall scripts, generated output, and apps catalog entry).
- Add an NVDA icon component and map entry for the Software page.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| frontend/pages/SoftwarePage/components/icons/Nvda.tsx | Adds NVDA icon component (base64 image embedded in SVG). |
| frontend/pages/SoftwarePage/components/icons/index.ts | Registers NVDA icon and maps "nvda" key to the new icon. |
| ee/maintained-apps/outputs/nvda/windows.json | Generated output for NVDA Windows maintained app (version/queries/scripts/sha). |
| ee/maintained-apps/outputs/docker-desktop/darwin.json | Updates Docker Desktop install script ref to the regenerated script content. |
| ee/maintained-apps/outputs/apps.json | Adds NVDA to the maintained apps catalog. |
| ee/maintained-apps/inputs/winget/scripts/nvda_uninstall.ps1 | Adds NVDA uninstall logic (registry lookup + silent uninstall + cleanup). |
| ee/maintained-apps/inputs/winget/scripts/nvda_install.ps1 | Adds NVDA install logic with timeout handling and registration verification. |
| ee/maintained-apps/inputs/winget/nvda.json | Adds winget input definition for NVDA Windows app. |
| ee/maintained-apps/inputs/homebrew/scripts/docker_desktop_install.sh | Implements the staging-dir removal + bounded wait to avoid Docker Desktop updater race. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| SECONDS=0 | ||
| while pgrep -f 'com\.docker\.install' >/dev/null 2>&1 && (( SECONDS < 30 )); do | ||
| sleep 1 | ||
| done |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.
Suppressed comments (1)
ee/maintained-apps/inputs/homebrew/scripts/docker_desktop_install.sh:134
- The new wait loop is capped at 30s, but after the loop the script proceeds even if the com.docker.install updater process is still running. That undermines the intent of "wait out any updater already in flight before touching /Applications/Docker.app" and can reintroduce the same race (mv/cp vs updater). Consider aborting the install if the updater is still running after the timeout, with a clear error message.
# Wait out any updater already in flight before touching /Applications/Docker.app.
SECONDS=0
while pgrep -f 'com\.docker\.install' >/dev/null 2>&1 && (( SECONDS < 30 )); do
sleep 1
done
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
WalkthroughThe Docker Desktop installer now removes staged self-update data before quitting Docker Desktop. It waits up to 30 seconds for active Possibly related PRs
✨ Finishing Touches📝 Generate docstrings
🧪 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 |
Related issue: Customer reports of failed Docker Desktop updates from self-service on macOS.
Details
The reported error is Docker Desktop's own updater speaking, not Fleet's:
Hosts showing "update available" in self-service are exactly the hosts where Docker Desktop has already downloaded and staged its own self-update at
~/Library/Application Support/com.docker.install/in_progress/Docker.app. When the FMA install script gracefully quits Docker Desktop, that quit triggers Docker's install-on-quit updater, which renamesDocker.app→Docker.app.backand moves the staged copy into place — racing the script's ownmv/rm/cpof/Applications/Docker.app. The script previously cleaned up after this race (leftover.backbundle and staged copy); this PR prevents it instead:com.docker.installstaging directory (staged bundle + updater state) before quitting the app, so the quit can't trigger Docker's updater. Same whole-directory removal the uninstall'spost_uninstall_scriptsalready does./Applications/Docker.app.go run ./cmd/maintained-apps -slug docker-desktop/darwin; version pinned at 4.85.0, installer URL/sha unchanged, only the install script ref changed.Hosts already wedged with a stale
Docker.app.backself-heal: the script still removes.backbefore copying the new bundle.Local validation (macOS arm64, Docker Desktop 4.84.0 running)
bash -nclean; embedded output script matches input byte-for-byte with correct sha256[:8] ref/Applications/Docker.app.back, stagedcom.docker.install/in_progress/Docker.app) and ran the shipped script: staging dir removed before quit, running Docker Desktop (VM + active build) quit gracefully, wait loop did not hangcom\.docker\.install: waits until it exits, 30s capChecklist for submitter
If some of the following don't apply, delete the relevant line.
SELECT *is avoided, SQL injection is prevented (using placeholders for values in statements), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters.Testing
Summary by CodeRabbit