zed-industries/zed · error · anyhow::Error
failed to fetch '{url}': status code {}
Error message
failed to fetch '{url}': status code {} What it means
Thrown by Zed's WebAssembly extension host (v0.1.0 WIT bindings) when an extension calls http_client::fetch and the server responds with a 4xx or 5xx status. The host deliberately converts any non-success HTTP status into an error string instead of returning the response, because the v0.1.0 WIT HttpResponse type has no way to carry an error body. Note that fetch_stream does NOT bail this way, so error responses with bodies must go through that API.
Source
Thrown at crates/extension_host/src/wasm_host/wit/since_v0_1_0.rs:312
async fn which(
&mut self,
delegate: Resource<Arc<dyn WorktreeDelegate>>,
binary_name: String,
) -> wasmtime::Result<Option<String>> {
latest::HostWorktree::which(self, delegate, 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,View on GitHub (pinned to 5a9b9558db)
Solutions
- Verify the exact URL the extension requests (it is echoed in the message) in a browser or with curl -I
- Handle 4xx/5xx bodies by switching to fetch_stream, which returns the response regardless of status
- For auth failures, supply the needed token/header in HttpRequest headers or use a public URL
- For rate limits, retry with backoff or use a conditional/etag-aware endpoint
Example fix
// before
let response = http_client::fetch(&request).await?; // Err("failed to fetch '...': status code 404 Not Found")
// after — read error bodies via fetch_stream
let stream = http_client::fetch_stream(&request).await?;
let response = stream.next().await; // status + body available even for 4xx/5xx Defensive patterns
Strategy: try-catch
Try / catch
// extension side: fetch returns Result<HttpResponse, String>
match http_client::fetch(&request).await {
Ok(response) => { /* 2xx/3xx only */ }
Err(e) if e.contains("status code") => {
// non-2xx: surface URL+status, fall back to cached asset or retry for 5xx
}
Err(e) => return Err(e), // transport-level failure
} Prevention
- Prefer fetch_stream when you need error bodies or status codes
- Cache downloaded artifacts keyed by URL so 404/403s degrade gracefully
- Log the URL from the error message before retrying so bad links are caught early
When it happens
Trigger: A v0.1.0 extension calling http_client::fetch against a URL that returns 404, 401, 403, 500, 502, etc. The bail fires inside the maybe! block when response.status().is_client_error() || response.status().is_server_error().
Common situations: Extension downloading a release asset from a moved/renamed GitHub URL, hitting an API rate limit (403/429), an expired pre-signed S3 link, a typo in the download URL, a proxy returning 502, or auth missing for a private repo.
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/4967abedfed205ae.
Report an issue: GitHub.