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

> Explore how the OpenWork Electron desktop app manages auto-updates with lazy-loaded electron-updater, manual approval, and automatic install for reliable cross-platform updates.

- Repository: [Different AI/openwork](https://github.com/different-ai/openwork)
- Tags: deep-dive
- Published: 2026-08-01

---

**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:

```javascript
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:

```javascript
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:

- **Capability declaration**: [`platform-capabilities.ts`](https://github.com/different-ai/openwork/blob/main/platform-capabilities.ts) sets `autoUpdate: true` for desktop builds
- **Settings page**: [`settings-page.tsx`](https://github.com/different-ai/openwork/blob/main/settings-page.tsx) checks this flag at line 192-196 before adding the tab

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:

```typescript
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:

```tsx
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.