How OpenWork Desktop Handles Auto-Updates: A Complete Technical Breakdown

The OpenWork Electron desktop app uses lazy-loaded electron-updater with manual download approval, automatic quit-and-install, and full-package downloads to ensure reliable cross-platform updates.

The auto-update system lives in apps/desktop/electron/updater.mjs and integrates closely with the React-based settings UI. This article explains the complete implementation—from lazy module loading to the end-user experience—based on the different-ai/openwork source code.

Lazy Loading the Auto-Updater

OpenWork defers loading electron-updater until an update check is actually requested. This reduces startup time and memory footprint.

The lazy loader in updater.mjs uses a simple memoization pattern:

let autoUpdaterInstance = null;
let autoUpdaterLoaded = false;

if (autoUpdaterLoaded) return autoUpdaterInstance;
autoUpdaterLoaded = true;

const mod = await import("electron-updater");
autoUpdaterInstance = mod.autoUpdater ?? mod.default?.autoUpdater ?? null;

This dynamic import runs only when loadAutoUpdater() is called, ensuring the heavy update logic doesn't burden normal app launches.

Core Configuration Strategy

Once loaded, the updater is configured for explicit user consent and safe defaults:

Setting Value Purpose
autoDownload false Requires user approval before downloading any update
autoInstallOnAppQuit true Seamlessly installs updates on next app exit
disableDifferentialDownload true Forces full package downloads for compatibility

These assignments occur immediately after module load in updater.mjs. The autoDownload: false setting is particularly important—it ensures users on metered connections or with specific version requirements aren't surprised by background downloads.

Event-Driven Update Lifecycle

The system listens to four key autoUpdater events:

autoUpdaterInstance.on("error", err => { /* log & surface to UI */ });
autoUpdaterInstance.on("update-available", info => { /* prompt user */ });
autoUpdaterInstance.on("download-progress", info => { /* update progress bar */ });
autoUpdaterInstance.on("update-downloaded", () => { /* notify ready to install */ });

This event wiring enables the React frontend to display real-time status: checking → available → downloading (with percent) → ready to install.

Applying Update Feeds by Channel

The applyElectronUpdaterFeed function receives a release channel (stable, beta, etc.) and configures the feed URL accordingly. This separation of loading (loadAutoUpdater) from feed application (applyElectronUpdaterFeed) allows the same updater instance to switch channels dynamically—useful for beta testers who want to toggle between release streams without restarting the app.

UI Integration and Capability Detection

The settings page conditionally renders an "Updates" tab based on platform capabilities:

This architecture prevents the updates UI from appearing in unsupported contexts (web builds, enterprise-locked deployments, etc.).

Triggering Updates from the Frontend

Here's how the React settings page initiates an update check:

import { loadAutoUpdater } from '@/electron/updater';

const checkForUpdates = async () => {
  const updater = await loadAutoUpdater();
  if (updater) {
    await updater.checkForUpdates();
  }
};

The loadAutoUpdater function returns null if the module fails to load or the platform doesn't support updates, so callers must always null-check.

Real-Time Progress Display

A complete React component implementing the update UI:

import { useEffect, useState } from 'react';
import { loadAutoUpdater } from '@/electron/updater';

export function UpdateStatus() {
  const [status, setStatus] = useState('Idle');
  const [percent, setPercent] = useState(0);

  useEffect(() => {
    loadAutoUpdater().then(updater => {
      if (!updater) {
        setStatus('Not available');
        return;
      }

      updater.on('update-available', () => {
        setStatus('Update available – click to download');
      });

      updater.on('download-progress', (info) => {
        setStatus(`Downloading: ${info.percent.toFixed(1)}%`);
        setPercent(info.percent);
      });

      updater.on('update-downloaded', () => {
        setStatus('Ready to install – restart to apply');
      });

      updater.checkForUpdates();
    });
  }, []);

  return (
    <div>
      <p>{status}</p>
      {percent > 0 && percent < 100 && (
        <progress value={percent} max={100} />
      )}
    </div>
  );
}

Test Coverage

The lazy-loading logic and event firing are verified in updater.test.mjs, which mocks the electron-updater module to test:

  • Multiple calls to loadAutoUpdater return the same instance (singleton behavior)
  • Event handlers are properly registered
  • Null-safe behavior when the module fails to load

Summary

  • Lazy loading in updater.mjs defers electron-updater import until needed
  • Conservative defaults require user approval (autoDownload: false) but enable quit-and-install (autoInstallOnAppQuit: true)
  • Full downloads are enforced via disableDifferentialDownload: true for cross-platform reliability
  • Event-driven architecture lets the React UI reflect real-time update status
  • Capability gating ensures updates UI only appears on supported desktop builds

Frequently Asked Questions

How do I disable auto-updates in OpenWork desktop?

Set autoUpdate: false in your platform capabilities configuration or build with ELECTRON_SKIP_AUTOUPDATE=1. The loadAutoUpdater function will return null, and the settings page will automatically hide the Updates tab.

Why does OpenWork download full packages instead of differential updates?

The disableDifferentialDownload: true setting prioritizes reliability over bandwidth efficiency. Differential updates can fail in mixed runtime environments (various Electron versions, custom build pipelines), so OpenWork opts for predictable full-package downloads.

Can users switch between stable and beta channels?

Yes. The applyElectronUpdaterFeed function accepts a channel parameter. The settings UI can call this with "beta" or "stable" to reconfigure the feed URL dynamically without restarting the app.

What happens if an update download fails mid-progress?

The error event fires with details, and the UI resets to the "check for updates" state. Downloads do not automatically resume; users must trigger checkForUpdates() again. Partial downloads are discarded—there is no retry logic in the current implementation.

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 →