Repository navigation
Let the desktop app download its own updates and install them when you quit, when automatic updates are on - #1074
Merged
Merged
Conversation
sak0a
force-pushed
the
port/desktop-install-on-quit
branch
from
October 31, 2026 14:20
1166cd1 to
73f94c1
Compare
…u quit, when automatic updates are on With a node's "Update automatically" setting on (automaticUpdates, owner only, together with update checks; never on a dev server), the desktop app that hosts the node now updates itself too: - The backend tells Desktop main the policy with a new `app-update-policy` frame on their private pipe, at start and on every change. Main takes it only from the backend generation it launched (no renderer IPC carries it), and it turns off when that backend exits until the next one reports, so it fails closed. - The startup (15 s) and poll (4 h) checks then also download what they find, holding one reservation over the check and its download; a scheduled check that comes meanwhile joins it. - Windows (NSIS) and Linux AppImage arm electron-updater's autoInstallOnAppQuit before the download, so a normal quit installs the update silently without starting the app again. macOS never arms it: Squirrel.Mac stages an armed update irreversibly and the unsigned installer replaces the bundle, so the download waits for Restart. - A version whose download or install failed is not downloaded or armed again in the background. Turning the policy off, a failed install and a relaunch disarm the install at quit. - Every install writes desktop-update-install.json at Restart or quit. The next launch shows "Updated to X" once, or, still on the old version, skips that version in the background. - Nodes advertise what their desktop app does (`desktopAppUpdates`: install-on-quit or restart), and Settings, Updates says it on the "Update automatically" row of desktop app nodes. Channel and flavor checks, allowDowngrade = false and electron-updater's signature and sha512 verification are unchanged. Port of T3 Code's unmerged background desktop updates (pingdotgg/t3code#17588), behind the owner's setting instead of always on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e launchers working after an unattended update - A quit during a system shutdown, a Windows logoff or after SIGTERM disarms the install at quit; the update installs at a later normal quit. - Only the version whose download completed while armed is armed again: electron-updater's quit installer installs whatever was downloaded last. - A Linux AppImage named with its version (as the releases are) now waits for Restart, like macOS: electron-updater would move the update to a new file name that nothing starts. The node's capability and the settings text follow. - The install record accepts only version-shaped values (semver, at most 64 characters), so the "Updated to X" notice shows nothing else. - A new backend turns the policy off until it reports. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sak0a
force-pushed
the
port/desktop-install-on-quit
branch
from
October 31, 2026 14:48
73f94c1 to
c1c7821
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Built on #1067 and #1072 (both merged).
What Changed
app-update-policyframe on their private pipe: once at start, then on every change. It sends "off" on a dev server and when it cannot read its settings.autoInstallOnAppQuitbefore the download starts. A normal quit then installs the update silently. The app does not start again on its own.desktop-update-install.jsonin the state directory. On the next launch:desktopAppUpdates:install-on-quitorrestart; absent on CLI nodes and development builds; the app and its backend decide it with the same shared function). In Settings → Updates, the "Update automatically" row of a desktop app node says what happens on its platform.docs/release.mddescribes the new behaviour.Why
This is the last phase (U4) of opt-in unattended updates. Until now, every desktop update needed two clicks: download, then restart. This PR ports the mechanism of T3 Code's unmerged pingdotgg/t3code#17588: background download on startup and poll checks, install at quit except on macOS, no background retry of a failed version, and an install record written at quit. Unlike T3, it stays off unless the node's owner turns it on.
Security review
automaticUpdates && enableProviderUpdateChecks, and never on a dev server. The setting is off by default and owner-only (Update provider CLIs on their own while nobody uses Ryco, when a node's owner turns it on #1067), and Agent Control can neither read nor change it. The app starts with the policy off. It turns off again when the backend exits, until the next backend reports, and the backend reports off when it cannot read its settings. With the policy off, behaviour is exactly as before:autoInstallOnAppQuitstays false and checks never download.autoInstallOnAppQuitand writes no install record.desktopAppAutomaticUpdateModereturnsrestarton macOS, and the app then never setsautoInstallOnAppQuit. Only with that flag on does electron-updater's MacUpdater hand the update to Squirrel.Mac. The unsigned installer still runs only from the explicit install path.install(isSilent = true, isForceRunAfter = false), and only on a quit with exit code 0. NSIS gets no--force-run. AppImage runs withAPPIMAGE_EXIT_AFTER_INSTALL, so the AppRun script does not start the app.relaunchDesktopAppdisarms before itsapp.exit(0), which still emitsquit. Nothing callsquitAndInstallunattended.update-availablehandler: the version must matchDESKTOP_UPDATE_CHANNELand the files must belong to the app's flavor, so Ryco and Ryco Nightly never cross.allowDowngrade = falseandautoDownload = falseare unchanged, and the download is the existingdownloadAvailableUpdatewith its watchdog. electron-updater's sha512 check and the Windows publisher signature check are untouched.AutomaticAppUpdates.report(child, …)drops any other child). No preload or renderer IPC carries it, and a source-boundary test pins that. A remote client can only reach it by changing the owner-only setting on the node.devUrlis set, and it does not advertisedesktopAppUpdates. The desktop updater itself is disabled in development and unpackaged builds (getAutoUpdateDisabledReason), so nothing is checked or downloaded there.desktopAppUpdatescapability appears in the pre-auth environment descriptor. It is a two-value literal, and the descriptor already showsplatform.os.hostedBoundaries.test.tslists it.Validation
bun run checkpasses.bun run check:changedpasses except the two known local-only failures (ProjectionPipeline.followUpQueue.test.ts,026_CanonicalizeModelSelectionOptions.test.ts).automaticAppUpdates.test.ts(17): with the policy off, a check only checks; with it on, a check downloads and arms on Windows/AppImage; macOS downloads but never arms; a failed download is not repeated; turning the policy off disarms and turning it on re-arms; a download that finished while disarmed is never armed, nor a later one after an armed one; a quit during a shutdown or logout disarms and records nothing; a failed install is never armed again; a version from a not-applied install record is skipped; one reservation over the check and its download; a report from any backend but the current one is ignored; a new backend turns the policy off; the policy turns off when the backend exits but stays when the quit stopped it; a relaunch disarms.updateInstallRecord.test.ts(5): the record turns into "updated", "not applied" or nothing, and only from semver values of at most 64 characters; it is settled and removed, kept while the install did not apply; a failed write returns false.desktopControlBoundary.test.ts: the policy only comes from the child's pipe and never from preload; the quit hook passes the shutdown, session-end and SIGTERM state.desktopAppUpdatePolicy.test.ts(4): the policy is sent at start and on changes; a change during the first read is not overwritten by the older read; unreadable settings and dev servers report off.ServerEnvironment.test.ts: the capability for desktop mode, and its absence in web mode and dev builds.AutomaticUpdatesSettings.logic.test.ts(2),updatedNotice.logic.test.ts(2), and theUpdatesSettings.browser.tsxrow text (browser test passes).main.ts, oldServerEnvironment.ts, old row component; macOS arming, retrying failed versions, no reservation, accepting any child, keeping the arm when the policy turns off, and never arming. New modules fail to import without the change.desktopMainBundleSplitting.test.tspasses: electron-updater stays a lazyrequire.updates/desktopAppUpdatePolicy.tsreporter as boot. After the review fixes and the rebase onto Let CLI service nodes update Ryco itself while nobody uses them, when automatic updates are on #1072: 7,009,877 B (the fixes added 204 B, the AppImage name check in the shared module).Review
An independent adversarial review found nothing critical or high. Fixed, each with a test that fails without it (checked by reverting or mutating the fix):
automaticAppUpdates.test.ts(quit during shutdown) anddesktopControlBoundary.test.ts(the hook's wiring, SIGTERM sets its flag even while already quitting).docs/release.mdfollow. Test: shared mode with versioned and unversioned AppImage names.updateInstallRecord.test.ts.desktopAppUpdatescapability is visible before sign-in. It says only desktop app vs CLI node and, on Linux, whether the app runs from an AppImage. The descriptor already shows the platform, and the coordinator decided to keep it.🤖 Generated with Claude Code