{"record":{"id":"21fb57023702fc60","repo":"Hmbown/CodeWhale","slug":"registry-source-name-cannot-be-fetched-as-a-pl","errorCode":null,"errorMessage":"registry source '{name}' cannot be fetched as a plain tarball","messagePattern":"registry source '(.+?)' cannot be fetched as a plain tarball","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/skills/install.rs","lineNumber":1023,"sourceCode":"}\n\n/// Resolve a *remote* [`InstallSource`] (GitHub repo or direct tarball URL)\n/// and download the first reachable candidate under the network policy.\n/// Registry sources are rejected: skill registry resolution stays inside\n/// [`candidate_urls`], and the plugin install on-ramp has no registry index.\npub(crate) async fn fetch_tarball(\n    source: &InstallSource,\n    network: &NetworkPolicy,\n    max_size: u64,\n) -> Result<FetchOutcome> {\n    let urls = match source {\n        InstallSource::GitHubRepo(repo) => vec![\n            format!(\"https://github.com/{repo}/archive/refs/heads/main.tar.gz\"),\n            format!(\"https://github.com/{repo}/archive/refs/heads/master.tar.gz\"),\n        ],\n        InstallSource::DirectUrl(url) => vec![url.clone()],\n        InstallSource::Registry(name) => {\n            bail!(\"registry source '{name}' cannot be fetched as a plain tarball\")\n        }\n    };\n    Ok(\n        match download_first_success(&urls, network, max_size).await? {\n            DownloadOutcome::Bytes { bytes, url } => FetchOutcome::Bytes { bytes, url },\n            DownloadOutcome::NeedsApproval(host) => FetchOutcome::NeedsApproval(host),\n            DownloadOutcome::Denied(host) => FetchOutcome::Denied(host),\n        },\n    )\n}\n\n/// Resolve the source spec into one or more candidate URLs to try in order.\nasync fn candidate_urls(\n    source: &InstallSource,\n    network: &NetworkPolicy,\n    registry_url: &str,\n) -> Result<UrlResolution> {\n    match source {","sourceCodeStart":1005,"sourceCodeEnd":1041,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/skills/install.rs#L1005-L1041","documentation":"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.","triggerScenarios":"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.","commonSituations":"New code shortcutting resolution, refactors that lose the resolve-then-fetch ordering, and tests exercising fetch_tarball with fixture sources.","solutions":["Resolve first: call candidate_urls(&source, ...) and fetch the resulting GitHub/DirectUrl source.","Keep the resolve-then-fetch ordering in one place so future call sites cannot skip it.","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."],"exampleFix":"// before\nlet source = InstallSource::Registry(name.clone());\nlet outcome = fetch_tarball(&source, &network, MAX).await?;\n\n// after\nlet urls = candidate_urls(&source, &network, registry_url).await?; // resolves to GitHub/DirectUrl\nlet outcome = fetch_tarball(&resolved_source, &network, MAX).await?;","handlingStrategy":"validation","validationCode":"match &source {\n    InstallSource::Registry(_) => {\n        // resolve through candidate_urls first; fetch_tarball cannot handle this\n    }\n    _ => { /* fetch_tarball is safe here */ }\n}","typeGuard":"fn is_tarball_fetchable(source: &InstallSource) -> bool {\n    !matches!(source, InstallSource::Registry(_))\n}","tryCatchPattern":null,"preventionTips":["Keep the resolve-then-fetch ordering in one code path.","Encode the invariant in the type: resolve to a ResolvedSource before exposing fetch_tarball.","Cover the Registry branch in fetch_tarball tests so refactors cannot silently drop the guard."],"tags":["skills","internal","api-misuse","registry","rust"],"backgroundTag":"unresolved-source-type","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}