zed-industries/zed · error · anyhow::Error

error deserializing latest release: {err:?}

Error message

error deserializing latest release: {err:?}

What it means

After the 4xx check passes, the release lister tries serde_json::from_slice::<Vec<GithubRelease>> on the body and bails when deserialization fails (it also logs the raw response text at error level). The body is valid HTTP 2xx/5xx content but not the expected array-of-releases shape: a 5xx HTML error page (server errors are NOT excluded by is_client_error), a proxy/interceptor payload, or a changed GitHub schema all trigger it.

Source

Thrown at crates/http_client/src/github.rs:74

    if response.status().is_client_error() {
        let text = String::from_utf8_lossy(body.as_slice());
        bail!(
            "status error {}, response: {text:?}",
            response.status().as_u16()
        );
    }

    let releases = match serde_json::from_slice::<Vec<GithubRelease>>(body.as_slice()) {
        Ok(releases) => releases,

        Err(err) => {
            log::error!("Error deserializing: {err:?}");
            log::error!(
                "GitHub API response text: {:?}",
                String::from_utf8_lossy(body.as_slice())
            );
            anyhow::bail!("error deserializing latest release: {err:?}");
        }
    };

    let mut release = releases
        .into_iter()
        .filter(|release| !require_assets || !release.assets.is_empty())
        .find(|release| release.pre_release == pre_release)
        .context("finding a prerelease")?;
    release.assets.iter_mut().for_each(|asset| {
        if let Some(digest) = &mut asset.digest
            && let Some(stripped) = digest.strip_prefix("sha256:")
        {
            *digest = stripped.to_owned();
        }
    });
    Ok(release)
}

View on GitHub (pinned to f4178619ac)

Solutions

  1. Look at the logged 'GitHub API response text:' line to see what body was actually parsed
  2. Treat 5xx as an error before parsing (check !status.is_success() instead of only is_client_error) and retry with backoff
  3. Update the GithubRelease struct if GitHub changed the schema
  4. Bypass the proxy or add exceptions for api.github.com

Example fix

// before
if response.status().is_client_error() { /* bail */ }
let releases = serde_json::from_slice::<Vec<GithubRelease>>(&body)?;

// after
let status = response.status();
if !status.is_success() {
    let text = String::from_utf8_lossy(&body);
    anyhow::bail!("status error {}, response: {text:?}", status.as_u16());
}
let releases = serde_json::from_slice::<Vec<GithubRelease>>(&body)?;
Defensive patterns

Strategy: try-catch

Validate before calling

// reject non-2xx (including 5xx HTML pages) before parsing
let status = response.status();
if !status.is_success() { anyhow::bail!("status {}", status.as_u16()); }

Try / catch

match serde_json::from_slice::<Vec<GithubRelease>>(&body) {
    Ok(releases) => Ok(releases),
    Err(err) => {
        log::error!("response text: {:?}", String::from_utf8_lossy(&body));
        retry_with_backoff().await // 5xx pages are transient
    }
}

Prevention

When it happens

Trigger: GitHub returning 5xx (the code only bails on client errors, so HTML error pages reach the parser), a corporate proxy rewriting responses, an API response where tag_name/assets/tarball_url/zipball_url fields are missing or renamed.

Common situations: Transient GitHub incidents; behind MITM proxies that inject HTML; upstream API contract changes after GitHub deprecates fields; pagination payloads accidentally routed to this function.

Related errors


AI-assisted analysis of zed-industries/zed@f4178619ac (2026-08-20). Data as JSON: /api/errors/633176f9b0d4063b. Report an issue: GitHub.