You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Bug]: macOS automatic updates can lose or fail to replace the app bundle when installed on an external drive #31244
I searched existing issues and did not find a duplicate.
I included enough detail to reproduce or investigate the problem.
Area
apps/desktop
Steps to reproduce
Copy T3 Code.app to a writable external drive, for example /Volumes/ExternalDrive/T3 Code.app, and open it from there.
Let an automatic update download, then choose to restart and install it (quitAndInstall).
Let ShipIt be stopped partway through the install. On macOS 27 this happens when macOS terminates ShipIt after the app quits (Squirrel/Squirrel.Mac#336). It also happens when the drive sleeps or is disconnected during the install.
A second case: open T3 Code straight from the mounted disk image, or from Downloads so that macOS runs it from App Translocation, then try to install an update.
Expected behavior
The copy on the external drive is replaced in one step by the new version and relaunches. If the app cannot replace itself where it runs (disk image, read-only drive, App Translocation), automatic updates are off and Settings explains why. They should not fail at install time.
Actual behavior
Squirrel.Mac's ShipIt stages its work in NSTemporaryDirectory() on the startup disk (-[SQRLInstaller ownedTemporaryDirectoryURL]). When the app lives on another volume, $TMPDIR and the app have different st_dev. Moving the old bundle out and the new bundle in then fails rename() with EXDEV, and ShipIt falls back to copying the whole bundle across volumes and deleting the source. That is slow, and the install is no longer atomic. If ShipIt is stopped partway, the external drive is left with the app missing or half-written.
From App Translocation or a read-only disk image, the app still checks for and downloads updates. The install fails only inside quitAndInstall, after T3 Code has already stopped its backends.
The cross-volume failure mode above comes from reading Squirrel.Mac's SQRLInstaller.m. We do not have a ShipIt log from an interrupted install.
macOS 27.2, Apple Silicon (arm64). T3 Code (Nightly) desktop app on a writable APFS external volume, updated by the bundled Squirrel.Mac / electron-updater.
Logs or stack traces
# st_dev from stat on macOS 27.2
/ 16777231
$TMPDIR 16777231
mounted APFS volume 16777239
Workaround
Move T3 Code to /Applications on the startup disk and open it from there.
Model and harness: Claude Opus 5.5 (Claude Agent), reviewed by GPT-6.1 Sol (Codex).
PR #31248 already implements the full fix covering all four cases:
Writable external volume (bundle.dev !== temp.dev): stages the downloaded zip beside the app on the same volume (.T3 Code.update/extracted), verifies the extracted bundle against the running app's designated code-signing requirement via /usr/bin/codesign, and swaps the bundle in place using same-volume mv renames with rollback and signal hardening.
Read-only volumes (mounted DMG / read-only external drive) & App Translocation: disables automatic updates upfront before download/install and surfaces the reason in Settings → About.
Startup-volume permission checks (EACCES): startup-disk installs (bundle.dev === temp.dev, including /Applications and /Volumes/Macintosh HD) return ShipIt directly without a writable-access probe, preserving Squirrel.Mac's normal authorization flow.
Ordinary startup-volume installs: remain enabled and continue using electronUpdater.quitAndInstall unchanged.
Regression tests for all four cases are included in apps/desktop/src/updates/DesktopUpdates.test.ts and apps/desktop/src/updates/MacAppVolume.test.ts.
P2: the app-exit wait and the post-move retry budget are separate. A rerun over a stage holding previous.app finishes the swap, and launching from .T3 Code.update/previous.app (or from the bundle path with the bundle missing) puts the app back. Regression tests cover the drive returning after the helper exited. The swap is still two renames, not an atomic exchange.
P3: previous.app is kept until open -n succeeds and is restored and reopened if open fails. A test covers it.
Issue watch is handing this issue off because Julius owns #31261, which has a closing reference to it. At head 8da4750, its description explicitly limits the fix to read-only locations and leaves writable external-drive installs unchanged. The separate external-volume implementation is in #31248. No code changed or device tests ran in this automation thread; the interrupted external-volume install remains unverified here.
I’m frustrated that this issue still has no clear owner or resolution after I reported the bug, submitted #31248, addressed the review findings, and provided regression tests and verification evidence.
As of today, automatic updates still do not work for my external-drive installation. I have to replace the app manually for every update, and I’m frustrated by this recurring inconvenience.
The automated handoff above points to #31261, but it explicitly acknowledges that #31261 only covers read-only locations and leaves writable external-drive installs unchanged. The interrupted-update failure on a writable external drive is part of this issue. That handoff does not resolve it.
PR #31248 addresses the writable external-drive case as well. Its branch has been updated with main and all 22 checks are green, but there is still no clear maintainer decision.
Please clarify who is responsible for taking this issue forward and what the next step is:
I’m willing to work through concrete feedback. Leaving the issue open after a handoff that does not cover the full problem, with no clear next step, is not an adequate resolution.
Activity
fix(desktop): stage macOS updates on the app's own volume for externa…
atoz03 commentedon Nov 5, 2026
PR #31248 already implements the full fix covering all four cases:
bundle.dev !== temp.dev): stages the downloaded zip beside the app on the same volume (.T3 Code.update/extracted), verifies the extracted bundle against the running app's designated code-signing requirement via/usr/bin/codesign, and swaps the bundle in place using same-volumemvrenames with rollback and signal hardening.EACCES): startup-disk installs (bundle.dev === temp.dev, including/Applicationsand/Volumes/Macintosh HD) returnShipItdirectly without a writable-access probe, preserving Squirrel.Mac's normal authorization flow.electronUpdater.quitAndInstallunchanged.Regression tests for all four cases are included in
apps/desktop/src/updates/DesktopUpdates.test.tsandapps/desktop/src/updates/MacAppVolume.test.ts.fix(desktop): stage macOS updates on the app's own volume for externa…
atoz03 commentedon Nov 5, 2026
Update on #31248, now at 2a293e5:
previous.appfinishes the swap, and launching from.T3 Code.update/previous.app(or from the bundle path with the bundle missing) puts the app back. Regression tests cover the drive returning after the helper exited. The swap is still two renames, not an atomic exchange.previous.appis kept untilopen -nsucceeds and is restored and reopened ifopenfails. A test covers it.fix(desktop): stage macOS updates on the app's own volume for externa…
shivamhwp commentedon Nov 5, 2026
Issue watch is handing this issue off because Julius owns #31261, which has a closing reference to it. At head 8da4750, its description explicitly limits the fix to read-only locations and leaves writable external-drive installs unchanged. The separate external-volume implementation is in #31248. No code changed or device tests ran in this automation thread; the interrupted external-volume install remains unverified here.
atoz03 commentedon Nov 12, 2026
@juliusmarminge @maria-rcks
I’m frustrated that this issue still has no clear owner or resolution after I reported the bug, submitted #31248, addressed the review findings, and provided regression tests and verification evidence.
As of today, automatic updates still do not work for my external-drive installation. I have to replace the app manually for every update, and I’m frustrated by this recurring inconvenience.
The automated handoff above points to #31261, but it explicitly acknowledges that #31261 only covers read-only locations and leaves writable external-drive installs unchanged. The interrupted-update failure on a writable external drive is part of this issue. That handoff does not resolve it.
PR #31248 addresses the writable external-drive case as well. Its branch has been updated with main and all 22 checks are green, but there is still no clear maintainer decision.
Please clarify who is responsible for taking this issue forward and what the next step is:
I’m willing to work through concrete feedback. Leaving the issue open after a handoff that does not cover the full problem, with no clear next step, is not an adequate resolution.