Hmbown/CodeWhale · error

failed to download skill

Error message

failed to download skill (last status: {})

What it means

`download_first_success` tried all candidate URLs; every one either was denied/prompted (returned earlier) or failed with an HTTP error status. After the loop, if no bytes were obtained and no network-policy outcome applies, it bails reporting the last HTTP status seen, or `unknown` if no request ever completed.

Solutions

  1. Check the reported last HTTP status and hit the URL manually to diagnose
  2. Verify the repo's default branch is `main` or `master`, or install via a direct tarball URL
  3. Retry later if the server returned a 5xx; check auth/proxy if 403

Example fix

// before
install("github:acme/skill", &skills_dir, MAX, &network, false).await?; // 404/404 -> bail
// after
let src = InstallSource::parse("https://git.example.com/acme/skill/archive/refs/heads/trunk.tar.gz")?;
install_with_registry(src, &skills_dir, MAX, &network, false, &registry_url).await?;
Defensive patterns

Strategy: retry

Try / catch

match download_first_success(&urls, &network, MAX).await {
    Err(e) if e.to_string().contains("failed to download skill") => {
        // inspect last status, retry transient 5xx with backoff
        tokio::time::sleep(Duration::from_secs(2)).await;
        retry_with_backoff(|| download_first_success(&urls, &network, MAX)).await?;
    }
    other => other?,
}

Prevention

When it happens

Trigger: All candidate URLs (e.g. GitHub main-branch and master-branch tarballs, or registry-listed URLs) returned non-success, non-404 statuses — 500s, 403 rate limits, etc. — or no URL was even attempted.

Common situations: GitHub repo with a non-`main`/`master` default branch (both tarball URLs 404... note 404s are tracked as NotFound and folded into this final bail); private repo returning 403/404-ish statuses; server outage returning 5xx; corporate proxy blocking requests.

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 Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/d7c0e159ac7dacb4. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/skills/install.rs:1132

            DownloadAttempt::Bytes(bytes) => {
                return Ok(DownloadOutcome::Bytes {
                    bytes,
                    url: url.clone(),
                });
            }
            DownloadAttempt::NotFound(status) => {
                last_status = Some(status);
                continue;
            }
        }
    }
    if let Some(host) = denied_host {
        return Ok(DownloadOutcome::Denied(host));
    }
    if let Some(host) = prompt_host {
        return Ok(DownloadOutcome::NeedsApproval(host));
    }
    bail!(
        "failed to download skill (last status: {})",
        last_status
            .map(|s| s.to_string())
            .unwrap_or_else(|| "unknown".to_string())
    );
}

enum DownloadAttempt {
    Bytes(Vec<u8>),
    NotFound(reqwest::StatusCode),
}

/// Stream a URL into memory with a size cap. Aborts on the first read that
/// would push the buffer over `max_size * 4` (the *4 accounts for compression;
/// the unpack step still enforces `max_size` on the *uncompressed* bytes).
async fn download_with_cap(url: &str, max_size: u64) -> Result<DownloadAttempt> {
    let resp = reqwest_client()
        .get(url)

View on GitHub (pinned to 73e0f67d83)