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

  1. Inspect the anyhow error chain (err.chain()/root cause) — the actual per-branch failure is what was returned, not this sentinel
  2. If you genuinely hit this literal, debug the branches vec construction in download_repo (lines ~3087-3096) — something filtered every candidate
  3. 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

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.