Electron Auto-Updater in OpenWork: How It Works and How to Customize It
OpenWork uses electron-updater with a custom main-process module that channels releases from GitHub, supports stable/alpha channels, and includes a signed recovery installer fallback for corrupted updates.
OpenWork ships its desktop client as an Electron application with a production-ready auto-update system. The Electron updater mechanism handles version detection, release channel selection, and fallback recovery—all exposed through a clean IPC bridge to the React frontend.
What Is the Electron Updater in OpenWork?
The electron-updater module (autoUpdater API) powers the auto-update mechanism in OpenWork. Unlike the basic Electron autoUpdater, this implementation adds channel-based releases (stable vs. alpha), macOS Squirrel bundle fixes, and a recovery flow for failed updates.
Key capabilities include:
- Channel-aware updates – Users opt into
stable(default) oralpha(macOS-only) releases - Full-zip downloads – Disabled differential downloads for reliability
- Progress tracking – Real-time download percentage in the UI
- Signed recovery installers – Fallback when normal updates fail
Core Updater Architecture
The auto-update system spans the main process, IPC layer, and renderer. Here's how the pieces connect:
| File | Purpose |
|---|---|
apps/desktop/electron/updater.mjs |
Main-process implementation: version detection, feed URL building, autoUpdater configuration |
apps/desktop/electron/recovery.mjs |
Recovery installer download, verification, and execution |
apps/app/src/react-app/domains/settings/state/electron-updater-state.ts |
React hook (useElectronUpdaterState) managing UI state and IPC calls |
apps/app/src/react-app/domains/settings/pages/updates-view.tsx |
Settings page rendering update controls |
apps/app/src/app/lib/platform-capabilities.ts |
Declares autoUpdate capability (hides UI on non-Electron platforms) |
Version Detection and Channel Selection
In updater.mjs, the resolveAppVersion function detects the running version:
- Packaged builds: Uses
app.getVersion() - Development: Reads from
package.json
The release channel persists in electron-updater-channel.v1.json within app.getPath('userData'). The readElectronUpdaterChannel and writeElectronUpdaterChannel functions manage this state.
Feed URL Configuration
The ELECTRON_UPDATER_FEEDS object maps channels to GitHub release endpoints:
| Channel | Feed URL |
|---|---|
stable |
https://github.com/different-ai/openwork/releases/latest/download |
alpha |
https://github.com/different-ai/openwork/releases/download/alpha-macos-latest |
The applyElectronUpdaterFeed function applies the correct feed to the autoUpdater instance.
How to Check for Updates Programmatically
From the React Frontend
Use the useElectronUpdaterState hook to trigger update checks:
// In a React component
const {
checkForUpdates,
updateStatus,
downloadUpdate,
installUpdateAndRestart,
} = useElectronUpdaterState();
// Manual "Check for updates" button handler
const onCheck = async () => {
const result = await checkForUpdates(); // Returns status with version info if available
console.log(result.updateInfo?.version);
};
The hook calls window.electronAPI.invoke('openwork:updater:check'), which routes to the main-process handler.
Main-Process Handler
In updater.mjs, the IPC handler implements the check:
ipcMain.handle("openwork:updater:check", async (_event, rawChannel, rawTargetVersion) => {
const updater = await ensureAutoUpdater(); // Loads electron-updater with config
const channelState = await applyElectronUpdaterFeed(app, updater, targetVersion, manifestChannel);
const result = await updater.checkForUpdates(); // HTTP request to feed URL
return buildUpdateCheckResponse(result, channelState);
});
How to Download and Install Updates
Download Progress Tracking
The download step streams the full update zip with progress events:
// Frontend: initiate download
const onDownload = async () => {
const result = await downloadUpdate();
if (!result.ok) {
console.error("Download failed:", result.reason);
}
};
Main-process implementation in updater.mjs:
ipcMain.handle("openwork:updater:download", async () => {
const updater = await ensureAutoUpdater();
await updater.downloadUpdate(); // Emits "download-progress" events
return { ok: true };
});
Progress events forward to the renderer via autoUpdater.on('download-progress', ...), updating updateStatus.downloadedBytes and totalBytes in the hook.
Install and Restart
The quitAndInstall call swaps the bundle and relaunches:
// Frontend: install and restart
const onInstall = async () => {
await installUpdateAndRestart(); // App quits immediately on success
};
Main-process handler with macOS safety fix:
ipcMain.handle("openwork:updater:installAndRestart", async () => {
const updater = await ensureAutoUpdater();
await enableSquirrelDirectContentsWrite(); // Prevents "Failed to copy bundle" errors
updater.quitAndInstall(false, true); // isSilent=false, runAfter=true
return { ok: true };
});
How to Switch Release Channels
The setReleaseChannel function opts users into alpha releases:
await setReleaseChannel("alpha"); // UI call
Behind the scenes in updater.mjs:
ipcMain.handle("openwork:updater:setChannel", async (_event, rawChannel) => {
const channel = await writeElectronUpdaterChannel(app, rawChannel, manifestChannel);
const updater = await ensureAutoUpdater();
if (updater) {
preventPendingUpdaterInstall(updater); // Cancel any pending update
return applyElectronUpdaterFeed(app, updater, null, manifestChannel);
}
return updaterChannelState(app, channel, null, manifestChannel);
});
The channel change persists across app restarts via the JSON file in user data.
Recovery Mechanism for Failed Updates
When normal updates fail (corrupted bundles, signature mismatches), the recovery flow in recovery.mjs provides a fallback:
- Download signed installer – Fetches
OpenWork-Recovery.dmg(macOS) from release assets - Verify SHA-512 – Checks against embedded manifest hash
- Cache locally – Stores in
app.getPath('userData')/recovery/ - Launch via shell – Executes the installer and exits the app
Recovery IPC handlers use the openwork:recovery:* namespace, exposed through parallel hooks in the UI.
Auto-Updater Configuration Details
The ensureAutoUpdater function in updater.mjs applies these settings:
| Setting | Value | Purpose |
|---|---|---|
autoDownload |
false |
Manual download control for progress UI |
disableDifferentialDownload |
true |
Full-zip downloads only (blockmap skipped) |
SquirrelMacDirectContentsWrite |
true |
Fixes macOS bundle copy failures |
These settings prioritize reliability over download size, appropriate for OpenWork's GitHub-releases distribution model.
Summary
- Version detection uses
app.getVersion()in production,package.jsonin development—implemented inupdater.mjs - Channel persistence stores user preference in
electron-updater-channel.v1.jsonunder user data - Feed resolution maps
stableandalphachannels to distinct GitHub release URLs - AutoUpdater configuration disables differential downloads and enables macOS Squirrel fixes
- IPC bridge (
openwork:updater:*) exposes check, download, and install operations to the renderer - React integration through
useElectronUpdaterStatehook with state machine (idle→checking→available→downloading→ready) - Recovery fallback downloads signed installers when standard updates fail, implemented in
recovery.mjs
Frequently Asked Questions
How does OpenWork detect if auto-updates should run?
OpenWork checks app.isPackaged at startup. When false (development), updater.mjs exports no-op functions. When true, it loads the full electron-updater stack and registers IPC handlers.
Can users opt out of automatic update checks?
Yes. The useElectronUpdaterState hook accepts an autoCheck option. When false, the app skips the initial checkForUpdates() call on mount. Users manually trigger checks via the Settings UI.
Why does OpenWork disable differential downloads?
Differential downloads (blockmap-based) sometimes fail with GitHub releases due to CDN inconsistencies. Setting disableDifferentialDownload = true in ensureAutoUpdater ensures full-zip reliability at the cost of larger downloads.
What happens if the alpha channel is selected on Windows or Linux?
The alpha channel feed URL is macOS-specific (alpha-macos-latest). The applyElectronUpdaterFeed function returns an error state for non-macOS platforms, and the UI blocks channel selection via platform-capabilities.ts detection.
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 →