Skip to content

Defuse Docker Desktop's install-on-quit updater in macOS FMA install script - #50451

Merged
allenhouchins merged 3 commits into
mainfrom
fix-docker-desktop-fma-self-update-race
Aug 3, 2026
Merged

Defuse Docker Desktop's install-on-quit updater in macOS FMA install script#50451
allenhouchins merged 3 commits into
mainfrom
fix-docker-desktop-fma-self-update-race

Conversation

@allenhouchins

@allenhouchins allenhouchins commented Aug 3, 2026

Copy link
Copy Markdown
Member

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:

failed to back up /Applications/Docker.app before update: renaming (moving) file from /Applications/Docker.app to /Applications/Docker.app.back: rename /Applications/Docker.app /Applications/Docker.app.back: file exists

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 renames Docker.appDocker.app.back and moves the staged copy into place — racing the script's own mv/rm/cp of /Applications/Docker.app. The script previously cleaned up after this race (leftover .back bundle and staged copy); this PR prevents it instead:

  • Remove the entire com.docker.install staging directory (staged bundle + updater state) before quitting the app, so the quit can't trigger Docker's updater. Same whole-directory removal the uninstall's post_uninstall_scripts already does.
  • Wait out (bounded, 30s) any updater already in flight before touching /Applications/Docker.app.
  • Output regenerated via 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.back self-heal: the script still removes .back before copying the new bundle.

Local validation (macOS arm64, Docker Desktop 4.84.0 running)

  • shellcheck and bash -n clean; embedded output script matches input byte-for-byte with correct sha256[:8] ref
  • Downloaded the pinned 4.85.0 DMG; sha256 matches the manifest
  • Seeded affected-host state (non-empty /Applications/Docker.app.back, staged com.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 hang
  • Wait loop unit-tested against a live process matching com\.docker\.install: waits until it exits, 30s cap

Checklist for submitter

If some of the following don't apply, delete the relevant line.

  • Input data is properly validated, 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.
  • Timeouts are implemented and retries are limited to avoid infinite loops

Testing

  • QA'd all new/changed functionality manually

Summary by CodeRabbit

  • Bug Fixes
    • Improved Docker Desktop installation reliability by clearing stale update files before replacement.
    • Added handling for active updater processes, waiting up to 30 seconds before continuing.
    • Added final cleanup to remove leftover application backup or update artifacts after installation.
    • Updated the installer reference for Docker Desktop 4.85.0.

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.
Copilot AI review requested due to automatic review settings August 3, 2026 17:39
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Script Diff Results

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

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Script Diff Results

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

Copilot AI 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.

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.install staging 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.

Comment on lines +131 to +134
SECONDS=0
while pgrep -f 'com\.docker\.install' >/dev/null 2>&1 && (( SECONDS < 30 )); do
sleep 1
done
Copilot AI review requested due to automatic review settings August 3, 2026 17:43

Copilot AI 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.

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

@allenhouchins
allenhouchins marked this pull request as ready for review August 3, 2026 17:47
@allenhouchins
allenhouchins merged commit 183aa05 into main Aug 3, 2026
18 checks passed
@allenhouchins
allenhouchins deleted the fix-docker-desktop-fma-self-update-race branch August 3, 2026 17:47
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: b2024b39-9629-4467-8c70-3c7a7346513b

📥 Commits

Reviewing files that changed from the base of the PR and between 89acb97 and b1d8882.

📒 Files selected for processing (2)
  • ee/maintained-apps/inputs/homebrew/scripts/docker_desktop_install.sh
  • ee/maintained-apps/outputs/docker-desktop/darwin.json

Walkthrough

The Docker Desktop installer now removes staged self-update data before quitting Docker Desktop. It waits up to 30 seconds for active com.docker.install processes to exit before replacing the application. It also removes stale Docker.app.back copies after installation. Docker Desktop version 4.85.0 now references installer script 4afb9744.

Possibly related PRs

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-docker-desktop-fma-self-update-race

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.

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.

3 participants