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
- Look at the logged 'GitHub API response text:' line to see what body was actually parsed
- Treat 5xx as an error before parsing (check !status.is_success() instead of only is_client_error) and retry with backoff
- Update the GithubRelease struct if GitHub changed the schema
- 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
- Check is_success(), not just is_client_error(), before deserializing external API bodies
- Log the raw body next to the serde error (this code already does) so schema drift is diagnosable
- Pin API versions / keep the DTO struct in sync with the documented response shape
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
- Failed to parse example file: {} {error}
- status error {}, response: {text:?}
- Message method is undefined: ${JSON.stringify(message)}
- Message params.text is undefined: ${JSON.stringify(message)}
- Message params.options is undefined: ${JSON.stringify(messag
AI-assisted analysis of zed-industries/zed@f4178619ac (2026-08-20).
Data as JSON: /api/errors/633176f9b0d4063b.
Report an issue: GitHub.