farion1231/cc-switch · warning · anyhow::Error
所有分支下载失败
Error message
所有分支下载失败
What it means
Fallback error at the bottom of download_repo: returned only when the candidate-branch loop makes zero attempts (branches list empty), because any real per-branch failure is captured in last_error and that concrete error is returned instead. With the current construction the list always contains at least 'main' and 'master', so this literal is effectively unreachable — callers normally see the last branch's DOWNLOAD_FAILED/timeout/parse error.
Source
Thrown at src-tauri/src/services/skill.rs:3119
"https://github.com/{}/{}/archive/refs/heads/{}.zip",
repo.owner, repo.name, branch
);
Self::assert_github_archive_url(&url, &repo.owner, &repo.name)?;
match self.download_and_extract(&url, &temp_path).await {
Ok(_) => return Ok((temp_dir, branch.to_string())),
Err(e) => {
// 每个分支各自重算预算,所以失败后必须把上一轮的残留清掉——
// 否则 N 个候选分支等于 N 倍的落盘量堆在同一个目录里。
let _ = fs::remove_dir_all(&temp_path);
let _ = fs::create_dir_all(&temp_path);
last_error = Some(e);
continue;
}
}
}
Err(last_error.unwrap_or_else(|| anyhow::anyhow!("所有分支下载失败")))
}
/// 下载并解压 ZIP
async fn download_and_extract(&self, url: &str, dest: &Path) -> Result<()> {
let client = crate::proxy::http_client::get();
let response = client.get(url).send().await?;
if !response.status().is_success() {
let status = response.status().as_u16().to_string();
return Err(anyhow::anyhow!(format_skill_error(
"DOWNLOAD_FAILED",
&[("status", &status)],
match status.as_str() {
"403" => Some("http403"),
"404" => Some("http404"),
"429" => Some("http429"),
_ => Some("checkNetwork"),
},
)));View on GitHub (pinned to a2e22f3302)
Solutions
- Inspect the anyhow error chain (err.chain()/root cause) — the actual per-branch failure is what was returned, not this sentinel
- If you genuinely hit this literal, debug the branches vec construction in download_repo (lines ~3087-3096) — something filtered every candidate
- Improve the message to include the candidates so future hits are self-describing
Example fix
// before
Err(last_error.unwrap_or_else(|| anyhow::anyhow!("所有分支下载失败")))
// after — make the sentinel self-describing if it ever fires
Err(last_error.unwrap_or_else(|| {
anyhow::anyhow!("所有分支下载失败: candidates={branches:?}")
})) Defensive patterns
Strategy: try-catch
Try / catch
// Rust — walk the anyhow chain; the real cause is a per-branch error, not the sentinel
if let Err(e) = service.download_repo(&repo).await {
let root = e.chain().last().map(|c| c.to_string()).unwrap_or_default();
log::warn!("repo download failed, root cause: {root}");
// branch on root: DOWNLOAD_FAILED/DOWNLOAD_TIMEOUT/ARCHIVE_* each need different UX
} Prevention
- Log per-branch attempts and their errors instead of only the aggregate failure
- When debugging a reported '所有分支下载失败', first check whether the string is the literal sentinel or a per-branch error that replaced it
- Keep the candidate list construction ('main'/'master' fallback) intact in refactors — the sentinel only fires when it is emptied
When it happens
Trigger: Zero iterations of the loop over branches: currently impossible since 'main'/'master' are unconditionally pushed (even repo.branch == "HEAD" still yields ["main","master"]). Would only surface if the candidate-construction logic later filters all entries out.
Common situations: Confusion in logs: developers grep for this message after a failed download, but the real cause is the per-branch error that replaced it via last_error.unwrap_or_else. Also surfaces after refactors that add branch filtering.
Related errors
AI-assisted analysis of farion1231/cc-switch@a2e22f3302 (2026-08-16).
Data as JSON: /api/errors/570f8cd8b2a1c7bf.
Report an issue: GitHub.