rustdesk/rustdesk · error

[root-update] Failed to download: {}

Error message

[root-update] Failed to download: {}

What it means

The update .dmg is downloaded as root via a strict HTTP client from the allowlisted URL. If the server responds with a non-success HTTP status, the temp directory is removed and `check_update_as_root` bails with the status code. The update is aborted; the currently installed version keeps running.

Source

Thrown at src/updater.rs:629

    let private_tmp = String::from_utf8(private_tmp_output.stdout)
        .map_err(|err| hbb_common::anyhow::anyhow!("[root-update] mktemp output error: {}", err))?
        .trim()
        .to_owned();
    if private_tmp.is_empty() {
        bail!("[root-update] mktemp returned an empty download directory");
    }
    {
        use std::os::unix::fs::PermissionsExt;
        std::fs::set_permissions(&private_tmp, std::fs::Permissions::from_mode(0o700))?;
    }
    let filename = dmg_url.split('/').last().unwrap_or("rustdesk.dmg");
    let file_path = std::path::PathBuf::from(format!("{}/{}", private_tmp, filename));
    let tmp_path = file_path.to_string_lossy().to_string();
    // Download
    let mut response = client.get(&dmg_url).send()?;
    if !response.status().is_success() {
        let _ = std::fs::remove_dir_all(&private_tmp);
        bail!("[root-update] Failed to download: {}", response.status());
    }
    // Create file exclusively (O_EXCL) and stream response directly into it
    {
        let mut file = std::fs::OpenOptions::new()
            .write(true)
            .create_new(true)
            .open(&file_path)
            .map_err(|e| { let _ = std::fs::remove_dir_all(&private_tmp); e })?;
        std::io::copy(&mut response, &mut file)
            .map_err(|e| { let _ = std::fs::remove_dir_all(&private_tmp); e })?;
    }
    log::info!("[root-update] Downloaded to {}", tmp_path);
    // Recheck active sessions before installing — download can take minutes
    if !has_no_active_conns_ipc() {
        if let Err(e) = std::fs::remove_dir_all(&private_tmp) {
            log::warn!("[root-update] Failed to remove temp dir {}: {}", private_tmp, e);
        }
        bail!("[root-update] Active session started during download, deferring update.");

View on GitHub (pinned to 91c9fccbb0)

Solutions

  1. Note the HTTP status in the message; a 404 means the dmg asset `rustdesk-<version>-<arch>.dmg` doesn't exist for that tag — verify the asset name on the release page.
  2. For 403/429, wait for the GitHub rate limit to reset or use a mirrored source.
  3. Check proxy/firewall rules allow reaching the release download host.
  4. Retry the update later; the updater will attempt again on its schedule.
Defensive patterns

Strategy: retry

Validate before calling

// pre-check that the release asset exists before triggering root update
// curl -sIL '<dmg_url>' | head -1  -> expect HTTP/2 200

Try / catch

match check_update_as_root() {
    Err(e) if e.to_string().contains("Failed to download") => {
        log::warn!("update download failed, will retry later: {}", e);
        // schedule retry with backoff
    }
    other => other,
}

Prevention

When it happens

Trigger: `client.get(&dmg_url).send()` yields `response.status().is_success() == false` — e.g. 404 because the version tag has no matching release asset, 403/429 rate-limiting from GitHub, 5xx from the server, or a proxy returning an error page.

Common situations: GitHub release asset not yet published/renamed for the detected version; GitHub rate limiting; corporate proxy blocking github.com; captive portals returning HTML error pages with 4xx/5xx statuses.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


AI-assisted analysis of rustdesk/rustdesk@91c9fccbb0 (2026-09-10). Data as JSON: /api/errors/242a4566eff1ef98. Report an issue: GitHub.