# How Zed Handles Auto-Updates Across macOS, Linux, and Windows

> Discover how Zed achieves seamless auto-updates on macOS Linux and Windows using a unified system with specialized crates and OS native tools for smooth installation.

- Repository: [Zed Industries/zed](https://github.com/zed-industries/zed)
- Tags: internals
- Published: 2026-03-01

---

**Zed implements a unified auto-update system using three specialized crates—`auto_update`, `auto_update_helper`, and `auto_update_ui`—that poll the Zed Cloud for new releases, download platform-specific assets, and install them using OS-native tools like `rsync` on Unix or a helper process on Windows.**

The Zed editor's auto-update mechanism is designed to deliver seamless, cross-platform updates while respecting the constraints of each operating system. According to the `zed-industries/zed` source code, the system abstracts platform differences behind a common polling and download layer, delegating only the final installation step to OS-specific implementations.

## Core Architecture of Zed's Auto-Update System

Zed's update logic is partitioned into three distinct crates to maintain separation of concerns:

- **`auto_update`** – Contains the core logic in [`crates/auto_update/src/auto_update.rs`](https://github.com/zed-industries/zed/blob/main/crates/auto_update/src/auto_update.rs). It manages the `AutoUpdater` entity, handles polling intervals, version comparison, asset downloading, and delegates installation to platform-specific functions.
- **`auto_update_helper`** – A Windows-only helper crate located in `crates/auto_update_helper`. It provides the `finalize_auto_update_on_quit` function that runs after the main Zed process exits to replace the running executable, since Windows locks files during execution.
- **`auto_update_ui`** – Located in [`crates/auto_update_ui/src/auto_update_ui.rs`](https://github.com/zed-industries/zed/blob/main/crates/auto_update_ui/src/auto_update_ui.rs), this crate renders notification toasts when updates complete and provides the "View Release Notes" action.

## The Auto-Update Lifecycle

### Initialization and Polling

When Zed launches, `auto_update::init` creates a global `AutoUpdater` instance and starts a background polling loop if `AutoUpdateSetting` is enabled (default: `true`):

```rust
pub fn init(client: Arc<Client>, cx: &mut App) {
    let updater = AutoUpdater::new(version, client, cx);
    cx.set_global(GlobalAutoUpdate(Some(updater)));
    if AutoUpdateSetting::get_global(cx).0 {
        updater.start_polling(cx);
    }
}

```

*Source:* `crates/auto_update/src/auto_update.rs:71-85`

The `start_polling` method spawns an async task that calls `poll(UpdateCheckType::Automatic)` every hour (`POLL_INTERVAL`), sleeping between checks using the background executor.

### Version Detection and Comparison

The `poll` method triggers `AutoUpdater::update`, which queries the Zed Cloud for the latest release asset. The version comparison logic differs by release channel:

- **Stable/Preview** – Uses semantic versioning comparison (`fetched_version > installed_version`).
- **Nightly** – Compares build metadata (Git SHA) against the installed binary's SHA or cached value.

If `check_if_fetched_version_is_newer` determines the fetched version is newer, the updater transitions to the **Downloading** state.

### Downloading Release Assets

`download_release` streams the platform-specific asset (DMG for macOS, tar.gz for Linux, EXE for Windows) into a temporary file:

```rust
async fn download_release(
    target_path: &Path,
    release: ReleaseAsset,
    client: Arc<HttpClientWithUrl>
) -> Result<()> {
    let mut target_file = File::create(&target_path).await?;
    let mut response = client.get(&release.url, Default::default(), true).await?;
    smol::io::copy(response.body_mut(), &mut target_file).await?;
    Ok(())
}

```

*Source:* `crates/auto_update/src/auto_update.rs:72-86`

The temporary location is determined by `InstallerDir::new()`, which returns a `tempfile::tempdir()` on Unix systems and an `updates` folder adjacent to the executable on Windows.

### Platform-Specific Installation

After downloading, `install_release` dispatches to OS-specific implementations:

**macOS** – `install_release_macos` mounts the DMG using `hdiutil`, rsyncs the `Zed.app` bundle to the Applications folder (or original location), and uses a `MacOsUnmounter` RAII guard to ensure the disk image is detached.

**Linux** – `install_release_linux` extracts the tarball with `tar -xzf`, then uses `rsync` to copy the new `zed.app` bundle into `~/.local` or the original installation directory.

**Windows** – `install_release_windows` executes the downloaded installer with `/verysilent /update=true` flags. Since Windows prevents overwriting a running executable, the installer writes a flag file ([`versions.txt`](https://github.com/zed-industries/zed/blob/main/versions.txt)) and spawns `auto_update_helper.exe`. The helper's `finalize_auto_update_on_quit` function waits for the main Zed process to exit, then replaces the binary and relaunches Zed.

*Sources:*  
- macOS: `crates/auto_update/src/auto_update.rs:58-86`  
- Linux: `crates/auto_update/src/auto_update.rs:44-55`  
- Windows: `crates/auto_update/src/auto_update.rs:46-51` and `crates/auto_update_helper/src/updater.rs:46-68`

### Restart and Finalization

Once installation completes, the updater signals the application to restart:

```rust
if let Some(new_binary_path) = new_binary_path {
    cx.update(|cx| cx.set_restart_path(new_binary_path));
}

```

*Source:* `crates/auto_update/src/auto_update.rs:46-49`

On macOS and Linux, the restart occurs immediately. On Windows, the restart is delegated to the helper process, which reads the update flag and handles the relaunch after the main process terminates.

## User Interface Integration

When an update completes, `auto_update_ui::notify_if_app_was_updated` displays a notification toast. It checks a KV-store flag (`auto-updater-should-show-updated-notification`) set by the updater:

```rust
if let Some(content) = announcement_for_version(&version) {
    // Custom "What's new" announcement
} else {
    // Standard "Updated to <channel> <version>" with release notes button
}

```

*Source:* `crates/auto_update_ui/src/auto_update_ui.rs:53-115`

The UI also provides the `ViewReleaseNotesLocally` action, which fetches release notes from the Zed Cloud and opens them in an editor tab.

## Summary

- **Zed's auto-update system** is implemented across three crates: `auto_update` (core logic), `auto_update_helper` (Windows finalization), and `auto_update_ui` (notifications).
- **Polling mechanism** checks the Zed Cloud every hour via `AutoUpdater::poll`, comparing semantic versions (Stable/Preview) or Git SHAs (Nightly).
- **Platform-specific installation** uses `rsync` and `hdiutil` on macOS, `tar` and `rsync` on Linux, and a helper process on Windows to overcome file locking limitations.
- **Restart coordination** happens immediately on Unix systems, while Windows delegates to `finalize_auto_update_on_quit` in the helper crate to swap binaries after the main process exits.
- **UI notifications** are handled by `auto_update_ui`, showing toasts when `notify_if_app_was_updated` detects the update flag in the KV store.

## Frequently Asked Questions

### How does Zed handle auto-updates on Windows differently from macOS and Linux?

Windows prevents overwriting a running executable, so Zed uses a two-stage process. The main application downloads the update and spawns `auto_update_helper.exe`, which waits for the main process to exit. The helper's `finalize_auto_update_on_quit` function then replaces the binary and relaunches Zed. On macOS and Linux, the updater uses `rsync` to directly overwrite the application bundle while running, then triggers an immediate restart via `cx.set_restart_path`.

### What triggers Zed to check for updates automatically?

When Zed initializes, `auto_update::init` creates a global `AutoUpdater` instance. If `AutoUpdateSetting` is enabled (default: true), the system calls `start_polling`, which spawns an async loop that executes `poll(UpdateCheckType::Automatic)` every hour (`POLL_INTERVAL`). Users can also trigger manual checks via the `Check` action, which forces an immediate poll regardless of the timer.

### How does Zed determine if a downloaded version is newer than the installed version?

The comparison logic in `check_if_fetched_version_is_newer` differs by release channel. For **Stable** and **Preview** channels, Zed performs a standard semantic version comparison where a higher version number indicates a newer release. For the **Nightly** channel, Zed compares the Git SHA build metadata from the fetched release against the installed binary's SHA or a cached value from the previous update, ensuring nightly builds update to the latest commit.

### Where does Zed download updates before installing them?

Zed streams release assets into temporary directories created by `InstallerDir::new()`. On macOS and Linux, this returns a `tempfile::tempdir()` that persists for the duration of the installation process. On Windows, the downloaded installer executable is placed in an `updates` folder adjacent to the running `Zed.exe`. After successful installation, these temporary files are cleaned up, with the Windows helper process handling final cleanup after the binary swap completes.