Hmbown/CodeWhale · error

registry source '{name}' cannot be fetched as a plain tarbal

Error message

registry source '{name}' cannot be fetched as a plain tarball

What it means

fetch_tarball maps GitHubRepo and DirectUrl sources to concrete tarball URLs, but a Registry source has no tarball to fetch; registry entries must be resolved through candidate_urls, which consults the registry index for the real source. Calling fetch_tarball on a Registry variant is an internal API misuse, not a user input error.

Source

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

}

/// Resolve a *remote* [`InstallSource`] (GitHub repo or direct tarball URL)
/// and download the first reachable candidate under the network policy.
/// Registry sources are rejected: skill registry resolution stays inside
/// [`candidate_urls`], and the plugin install on-ramp has no registry index.
pub(crate) async fn fetch_tarball(
    source: &InstallSource,
    network: &NetworkPolicy,
    max_size: u64,
) -> Result<FetchOutcome> {
    let urls = match source {
        InstallSource::GitHubRepo(repo) => vec![
            format!("https://github.com/{repo}/archive/refs/heads/main.tar.gz"),
            format!("https://github.com/{repo}/archive/refs/heads/master.tar.gz"),
        ],
        InstallSource::DirectUrl(url) => vec![url.clone()],
        InstallSource::Registry(name) => {
            bail!("registry source '{name}' cannot be fetched as a plain tarball")
        }
    };
    Ok(
        match download_first_success(&urls, network, max_size).await? {
            DownloadOutcome::Bytes { bytes, url } => FetchOutcome::Bytes { bytes, url },
            DownloadOutcome::NeedsApproval(host) => FetchOutcome::NeedsApproval(host),
            DownloadOutcome::Denied(host) => FetchOutcome::Denied(host),
        },
    )
}

/// Resolve the source spec into one or more candidate URLs to try in order.
async fn candidate_urls(
    source: &InstallSource,
    network: &NetworkPolicy,
    registry_url: &str,
) -> Result<UrlResolution> {
    match source {

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Resolve first: call candidate_urls(&source, ...) and fetch the resulting GitHub/DirectUrl source.
  2. Keep the resolve-then-fetch ordering in one place so future call sites cannot skip it.
  3. If plain-tarball fetches for registry entries are genuinely needed, teach the registry format to carry tarball URLs explicitly instead of special-casing the client.

Example fix

// before
let source = InstallSource::Registry(name.clone());
let outcome = fetch_tarball(&source, &network, MAX).await?;

// after
let urls = candidate_urls(&source, &network, registry_url).await?; // resolves to GitHub/DirectUrl
let outcome = fetch_tarball(&resolved_source, &network, MAX).await?;
Defensive patterns

Strategy: validation

Validate before calling

match &source {
    InstallSource::Registry(_) => {
        // resolve through candidate_urls first; fetch_tarball cannot handle this
    }
    _ => { /* fetch_tarball is safe here */ }
}

Type guard

fn is_tarball_fetchable(source: &InstallSource) -> bool {
    !matches!(source, InstallSource::Registry(_))
}

Prevention

When it happens

Trigger: Code (or a test) constructing InstallSource::Registry(name) and calling fetch_tarball directly instead of resolving through candidate_urls first. The public install flow does not hit this; only a path that bypasses the resolution layer does.

Common situations: New code shortcutting resolution, refactors that lose the resolve-then-fetch ordering, and tests exercising fetch_tarball with fixture sources.

Related errors


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