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) or alpha (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:

  1. Download signed installer – Fetches OpenWork-Recovery.dmg (macOS) from release assets
  2. Verify SHA-512 – Checks against embedded manifest hash
  3. Cache locally – Stores in app.getPath('userData')/recovery/
  4. 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.json in development—implemented in updater.mjs
  • Channel persistence stores user preference in electron-updater-channel.v1.json under user data
  • Feed resolution maps stable and alpha channels 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 useElectronUpdaterState hook 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →