How OpenWork Implements Desktop Application Auto-Update Mechanisms: A Technical Deep Dive
OpenWork uses the electron-updater library wrapped in a custom IPC-based module to manage automatic updates across Windows, macOS, and Linux, with channel-based distribution and Squirrel.Mac safeguards for macOS.
The OpenWork desktop client is an Electron application that requires a robust mechanism to distribute updates seamlessly to users. According to the source code in the different-ai/openwork repository, the application implements a sophisticated auto-update workflow that handles channel management, version comparison, and platform-specific edge cases. This article examines the technical implementation found in apps/desktop/electron/updater.mjs and related files.
Architecture and Initialization
The auto-update system initializes only when the application is packaged (app.isPackaged), ensuring that development builds do not trigger update checks. The registerUpdaterIpc function serves as the entry point, lazily loading electron-updater via loadAutoUpdater and storing the resulting autoUpdaterInstance.
In apps/desktop/electron/updater.mjs (lines 236-244), the updater configuration disables auto-download, enables auto-install on quit, and disables differential downloads:
// Configuration within ensureAutoUpdater
autoUpdaterInstance.autoDownload = false;
autoUpdaterInstance.autoInstallOnAppQuit = true;
autoUpdaterInstance.disableDifferentialDownload = true;
Channel-Based Update Distribution
OpenWork supports multiple release channels (stable and alpha), allowing users to opt into pre-release builds. The system persists the selected channel in electron-updater-channel.v1.json within the user-data folder.
The readElectronUpdaterChannel and writeElectronUpdaterChannel functions (lines 59-74) handle this persistence, normalizing the channel based on the current platform and manifest. Depending on the selected channel, the electronUpdaterFeedUrl function selects the appropriate endpoint from ELECTRON_UPDATER_FEEDS—stable releases point to the latest release feed, while alpha channels point to pre-release feeds (lines 30-34).
The applyElectronUpdaterFeed function (lines 165-176) configures the updater instance with these parameters:
allowPrerelease: Set totruefor alpha channelsallowDowngrade: Enabled when switching from alpha back to stablesetFeedURL: Applies the computed feed URL to the updater instance
IPC Communication Interface
The main process exposes five IPC handlers that allow the renderer process to control the update lifecycle without direct access to the electron-updater module. These handlers are registered in registerUpdaterIpc (lines 199-281):
openwork:updater:getChannel: Retrieves current channel and version informationopenwork:updater:setChannel: Updates the channel preference and persists to diskopenwork:updater:check: Queries the feed for available updatesopenwork:updater:download: Downloads the update packageopenwork:updater:installAndRestart: Installs the downloaded update and relaunches
The Update Lifecycle
Checking for Updates
When the renderer invokes openwork:updater:check, the main process loads the updater via ensureAutoUpdater and calls updater.checkForUpdates(). The system then compares the retrieved version against the current application version using compareVersions and isVersionNewr utilities (lines 229-258) to determine availability.
Downloading Updates
The download phase (lines 283-306) first ensures the feed configuration is current, then calls cleanStaleUpdaterState to clear any cached Squirrel.Mac state that could block installation. Finally, it invokes updater.downloadUpdate() to retrieve the package.
Installation and Restart
After successful download, the installAndRestart handler (lines 308-322) enables the Squirrel.Mac DirectContentsWrite flag via enableSquirrelDirectContentsWrite to prevent bundle-move failures, then executes updater.quitAndInstall(false, true) to replace the application bundle and relaunch.
macOS-Specific Safeguards
macOS updates utilize Squirrel.Mac, which requires special handling for bundle replacement. The implementation includes two critical safety mechanisms:
enableSquirrelDirectContentsWrite: Sets theDirectContentsWriteflag to avoid "permission denied" errors during the atomic swap of application bundles (lines 113-131)cleanStaleUpdaterState: Removes cached update artifacts that could prevent future updates from applying correctly
These safeguards ensure reliable updates even when the application is installed in privileged directories like /Applications.
Renderer Integration Examples
The settings UI interacts with the update mechanism through the IPC bridge declared in apps/app/src/app/lib/platform-capabilities.ts. Below are the typical renderer-side calls:
// Retrieve current channel and version
const channelInfo = await window.electron.ipcRenderer.invoke(
"openwork:updater:getChannel"
);
// Returns: { channel: "stable", currentVersion: "2.5.0", ... }
// Switch to alpha channel for testing
await window.electron.ipcRenderer.invoke(
"openwork:updater:setChannel",
"alpha"
);
// Check for updates with optional target version
const checkResult = await window.electron.ipcRenderer.invoke(
"openwork:updater:check",
undefined, // retain existing channel
"2.6.0" // optional specific version target
);
// Download available update
const downloadResult = await window.electron.ipcRenderer.invoke(
"openwork:updater:download"
);
// Install and restart
await window.electron.ipcRenderer.invoke(
"openwork:updater:installAndRestart"
);
These calls map directly to the handlers defined in registerUpdaterIpc within apps/desktop/electron/updater.mjs.
Summary
- Lazy Initialization: The updater loads only in packaged builds via
registerUpdaterIpcandensureAutoUpdater, configured with specific auto-download and installation preferences. - Channel Management: Users can switch between
stableandalphachannels, with preferences persisted inelectron-updater-channel.v1.jsonand feed URLs dynamically configured viaapplyElectronUpdaterFeed. - IPC Architecture: Five dedicated IPC channels expose update functionality to the renderer while keeping
electron-updaterencapsulated in the main process. - Cross-Platform Safety: macOS-specific functions
enableSquirrelDirectContentsWriteandcleanStaleUpdaterStateprevent common Squirrel.Mac failure modes during bundle replacement. - Explicit Lifecycle: The implementation separates checking, downloading, and installation into distinct IPC calls, allowing the UI to provide progressive update status feedback.
Frequently Asked Questions
How does OpenWork handle update channel switching?
The application stores the selected channel (stable or alpha) in a JSON file located in the user-data directory via writeElectronUpdaterChannel. When switching channels, applyElectronUpdaterFeed reconfigures the updater instance with the new feed URL, adjusts allowPrerelease and allowDowngrade flags, and persists the preference for subsequent launches.
Why does the auto-update mechanism disable auto-download by default?
In apps/desktop/electron/updater.mjs, the configuration explicitly sets autoUpdaterInstance.autoDownload = false to give users control over when updates download, particularly important for users on metered connections or those who wish to review release notes before updating. The renderer must explicitly invoke the openwork:updater:download IPC channel to initiate retrieval.
What prevents macOS update failures in the OpenWork desktop application?
The implementation includes macOS-specific safeguards in enableSquirrelDirectContentsWrite and cleanStaleUpdaterState (lines 113-131). These functions enable the DirectContentsWrite Squirrel flag to avoid bundle-move permission errors and clear stale cached update states that could block future installations.
How does the renderer process communicate with the auto-updater?
The main process registers IPC handlers in registerUpdaterIpc that expose getChannel, setChannel, check, download, and installAndRestart operations. The renderer invokes these through window.electron.ipcRenderer.invoke(), allowing the UI to display update availability and progress while the main process handles all electron-updater interactions.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →