zed-industries/zed · error · anyhow::Error
failed to fetch '{url}': status code {}
Error message
failed to fetch '{url}': status code {} What it means
Same guard as in the v0.1.0 bindings, present in the v0.8.0 host: http_client::fetch bails whenever the response status is a client or server error, embedding the requested URL and the status. The v0.8.0 fetch, like v0.1.0, returns Ok(HttpResponse) only for 2xx/3xx; the error string is surfaced to the extension as the Err side of its Result. fetch_stream remains the escape hatch for reading non-2xx bodies.
Source
Thrown at crates/extension_host/src/wasm_host/wit/since_v0_8_0.rs:639
let delegate = self.table.get(&delegate)?;
Ok(delegate.which(binary_name).await)
}
async fn drop(&mut self, _worktree: Resource<Worktree>) -> wasmtime::Result<()> {
// We only ever hand out borrows of worktrees.
Ok(())
}
}
impl common::Host for WasmState {}
impl http_client::Host for WasmState {
async fn fetch(
&mut self,
request: http_client::HttpRequest,
) -> wasmtime::Result<Result<http_client::HttpResponse, String>> {
maybe!(async {
let url = &request.url;
let request = convert_request(&request)?;
let mut response = self.host.http_client.send(request).await?;
if response.status().is_client_error() || response.status().is_server_error() {
bail!("failed to fetch '{url}': status code {}", response.status())
}
convert_response(&mut response).await
})
.await
.to_wasmtime_result()
}
async fn fetch_stream(
&mut self,
request: http_client::HttpRequest,
) -> wasmtime::Result<Result<Resource<ExtensionHttpResponseStream>, String>> {
let request = convert_request(&request).into_wasmtime_result()?;
let response = self.host.http_client.send(request);View on GitHub (pinned to 5a9b9558db)
Solutions
- curl -I the exact URL from the message to see the real status and act on it (fix URL, add auth header, wait out rate limit)
- Switch to fetch_stream when the error body or status must be inspected
- Add retry with backoff for 5xx/429 responses
- Cache downloaded assets so transient failures degrade gracefully
Example fix
// before
let response = http_client::fetch(&request).await?; // Err("failed to fetch '...': status code 403 Forbidden")
// after
match http_client::fetch(&request).await {
Ok(response) => Ok(response),
Err(e) if e.contains("status code 4") || e.contains("status code 5") => {
Err(format!("download failed ({e}); check URL/auth"))
}
Err(e) => Err(e),
} Defensive patterns
Strategy: try-catch
Try / catch
match http_client::fetch(&request).await {
Ok(response) => Ok(response),
Err(e) if e.contains("status code 4") || e.contains("status code 5") => {
Err(format!("non-2xx response: {e}"))
}
Err(e) => Err(e),
} Prevention
- Validate/normalize URLs before fetching (no trailing spaces, correct release tag)
- Attach auth headers up front for private resources instead of discovering 401/403 at runtime
- Use fetch_stream when status or error body matters
When it happens
Trigger: An extension on the v0.8.0 API calling http_client::fetch against any URL returning 4xx/5xx (404 release asset, 403 rate-limited API, 500 server error).
Common situations: Download URLs that 404 after an upstream release rename, GitHub API secondary rate limits returning 403, expired tokens, or corporate proxies answering 502.
Related errors
- failed to fetch '{url}': status code {}
- Failed to fetch experiments: {:?} Body: {}
- DAP returned IPv6 host {addr}, which the v0.6.0 extension AP
- extension {} has invalid zed:api-version section: {:?}
- Unknown settings category: {}
AI-assisted analysis of zed-industries/zed@5a9b9558db (2026-08-20).
Data as JSON: /api/errors/f35032d9e18073f8.
Report an issue: GitHub.