{"record":{"id":"d7c0e159ac7dacb4","repo":"Hmbown/CodeWhale","slug":"failed-to-download-skill-last-status","errorCode":null,"errorMessage":"failed to download skill (last status: {})","messagePattern":"failed to download skill \\(last status: (.+?)\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/skills/install.rs","lineNumber":1129,"sourceCode":"            DownloadAttempt::Bytes(bytes) => {\n                return Ok(DownloadOutcome::Bytes {\n                    bytes,\n                    url: url.clone(),\n                });\n            }\n            DownloadAttempt::NotFound(status) => {\n                last_status = Some(status);\n                continue;\n            }\n        }\n    }\n    if let Some(host) = denied_host {\n        return Ok(DownloadOutcome::Denied(host));\n    }\n    if let Some(host) = prompt_host {\n        return Ok(DownloadOutcome::NeedsApproval(host));\n    }\n    bail!(\n        \"failed to download skill (last status: {})\",\n        last_status\n            .map(|s| s.to_string())\n            .unwrap_or_else(|| \"unknown\".to_string())\n    );\n}\n\nenum DownloadAttempt {\n    Bytes(Vec<u8>),\n    NotFound(reqwest::StatusCode),\n}\n\n/// Stream a URL into memory with a size cap. Aborts on the first read that\n/// would push the buffer over `max_size * 4` (the *4 accounts for compression;\n/// the unpack step still enforces `max_size` on the *uncompressed* bytes).\nasync fn download_with_cap(url: &str, max_size: u64) -> Result<DownloadAttempt> {\n    let resp = reqwest_client()\n        .get(url)","sourceCodeStart":1111,"sourceCodeEnd":1147,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/skills/install.rs#L1111-L1147","documentation":"After trying every candidate URL, download_first_success bails with the last non-404 HTTP status (or 'unknown' if none was recorded) when no bytes were obtained and no host was denied or left pending approval. It is the aggregate 'all downloads failed' error. 404 is deliberately treated as 'try the next candidate', so this fires on non-404 failures such as 403 or 5xx.","triggerScenarios":"Both GitHub archive candidates (main, then master) fail with 5xx/403 for a repo, or a direct URL host errors out, while the network policy allows the hosts. Because a non-404 error status aborts the loop immediately via '?', the reported 'last status' is the first hard failure.","commonSituations":"GitHub rate limiting (403) during bursts, private repos without credentials, artifact host outages, and transient proxy failures.","solutions":["Retry after a short wait; transient 5xx responses and rate limits clear on their own.","Act on the status: 403 points to rate limiting or a private repo (check auth); 5xx points to upstream trouble.","Verify the repo's default branch and pass a DirectUrl to that branch's exact archive URL.","If rate-limited, spread installs out or authenticate the requests."],"exampleFix":"# before\n/skill install github:owner/repo\n# -> failed to download skill (last status: 403 Forbidden)\n\n# after: point at the exact default-branch archive\n/skill install https://github.com/owner/repo/archive/refs/heads/trunk.tar.gz","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"const MAX_TRIES: u32 = 3;\nfor attempt in 1..=MAX_TRIES {\n    match skills::install::install(&spec, &dir, &network).await {\n        Err(err) if err.to_string().starts_with(\"failed to download skill\") && attempt < MAX_TRIES => {\n            tokio::time::sleep(std::time::Duration::from_secs(2u64.pow(attempt))).await;\n        }\n        other => return other.map(|_| ()),\n    }\n}","preventionTips":["Back off and retry on 5xx or rate-limit statuses; do not hammer the host.","Prefer exact-branch DirectUrls over the github: shorthand when the default branch is unusual.","Authenticate requests for private repos instead of relying on retries."],"tags":["skills","download","http","github","rust"],"backgroundTag":"http-download-failed","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}