Hmbown/CodeWhale · error

download {url} returned {status}

Error message

download {url} returned {status}

What it means

Raised by download_with_cap for a single URL that answered with a non-success status other than 404. 404 is swallowed as NotFound so the candidate loop can try alternates; every other error status (403, 401, 5xx) bails here with the URL and status in the message. Because the bail propagates through '?' in the candidate loop, remaining candidate URLs are not tried after it, unlike the 404 path.

Source

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

    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)
        .send()
        .await
        .with_context(|| format!("failed to GET {url}"))?;
    let status = resp.status();
    if !status.is_success() {
        if status == reqwest::StatusCode::NOT_FOUND {
            return Ok(DownloadAttempt::NotFound(status));
        }
        bail!("download {url} returned {status}");
    }
    // Soft cap on the *compressed* download — well above max_size to allow
    // for highly compressible payloads but still bounded.
    let compressed_cap = max_size.saturating_mul(4);
    let bytes = resp
        .bytes()
        .await
        .with_context(|| format!("failed to read body of {url}"))?;
    if (bytes.len() as u64) > compressed_cap {
        bail!("download {url} exceeds compressed size cap of {compressed_cap} bytes");
    }
    Ok(DownloadAttempt::Bytes(bytes.to_vec()))
}

struct StagedSkill {
    skill_name: String,
    staged_path: PathBuf,
}

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Act on the status: 401/403 means the artifact needs credentials or is private; 5xx means retry later.
  2. Fix or replace the URL; a moved artifact should get its new address.
  3. When using the github: shorthand and the first candidate errors (not 404), switch to a DirectUrl for the correct branch.

Example fix

# before
/skill install https://cdn.example.com/pack.tar.gz
# -> download ... returned 403 Forbidden

# after: artifact requires auth; use the public mirror
/skill install https://mirror.example.com/pack.tar.gz
Defensive patterns

Strategy: retry

Try / catch

for url in candidate_urls_after_failure {
    match download_with_cap(&url, max_size).await {
        Err(err) if err.to_string().contains("returned 403") || err.to_string().contains("returned 401") => {
            continue; // auth-gated: try the next candidate URL, retrying will not help
        }
        Err(err) if err.to_string().contains("returned 5") => {
            backoff_and_retry(&url).await; // transient server error
        }
        other => return other.map(|_| ()),
    }
}

Prevention

When it happens

Trigger: A direct-URL install pointing at a permission-gated artifact (403), an auth-required or expired signed URL (401), or a server error (500/502). With the github: shorthand, a non-404 error on the main-branch archive aborts before the master fallback is tried.

Common situations: Private or auth-required artifacts, rate-limited CDNs returning 403, expired signed URLs, and mirrors returning 502.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/f2511181f389f043. Report an issue: GitHub.