farion1231/cc-switch · error · anyhow::Error
DOWNLOAD_TIMEOUT
DOWNLOAD_TIMEOUT
Error message
DOWNLOAD_TIMEOUT
What it means
install() wraps the repo download in tokio's timeout(std::time::Duration::from_secs(60), self.download_repo(&repo)) and maps the Elapsed error to DOWNLOAD_TIMEOUT, a structured JSON error (owner, name, timeout="60", suggestion 'checkNetwork'). This is a hard 60-second wall-clock budget covering the entire git clone/download of the skill repository — not a socket idle timeout — so slow networks, huge repos, or stalled connections all surface identically. The double '?!' after map_err means the inner download's own errors propagate separately; only exceeding 60s yields this code.
Solutions
- Check connectivity and retry once the network improves — transient stalls are the most common cause.
- Disable/adjust VPN or proxy for the git host, or switch to a faster network, then retry the install.
- If the repo is genuinely large, install from a smaller mirror or the author's trimmed skill-only repo; the fixed 60s budget cannot be raised from the client.
- If it reproduces consistently on fast networks, verify the repo URL/branch exists (a hanging redirect can mimic a timeout).
Example fix
# before: 300MB skills monorepo over hotel Wi-Fi install(skill from "owner/huge-skills-monorepo") # -> DOWNLOAD_TIMEOUT owner=owner name=huge-skills-monorepo timeout=60 # after: install from the trimmed single-skill repo install(skill from "owner/pdf-tools") # clone finishes well under 60s
Defensive patterns
Strategy: retry
Validate before calling
// Pre-flight reachability of the git host before install
let reachable = tokio::net::TcpStream::connect((repo_host.as_str(), 443)).await.is_ok();
if !reachable { return early_hint("checkNetwork"); } Try / catch
let mut attempt = 0;
loop {
match install(&db, &skill, &app).await {
Err(e) if e.to_string().contains("DOWNLOAD_TIMEOUT") && attempt < 2 => { attempt += 1; tokio::time::sleep(Duration::from_secs(5)).await; continue; }
other => break other?,
}
} Prevention
- Retry once after a transient stall before surfacing the error to the user.
- Prefer small skill-only repos over monorepos for installation; keep proxies/VPN in mind on slow links.
When it happens
Trigger: Installing a skill from a repository that takes more than 60s to clone: dial-up/high-latency links, a multi-hundred-MB repo, rate-limited or throttled git hosts, a proxy that stalls the transfer, or DNS/TLS handshake hanging inside download_repo.
Common situations: Corporate proxies/VPNs slowing GitHub; cloning during CI bandwidth contention; slow first clone of a monorepo that bundles assets; intermittent Wi-Fi.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- INVALID_SKILL_DIRECTORY
- Skill 存储目录不能与 的 Skills 目录相同
- SKILL_DIR_NOT_FOUND
- SKILL_DIRECTORY_CONFLICT
- ${error}
AI-assisted analysis of farion1231/cc-switch@06082e189d (2026-08-20).
Data as JSON: /api/errors/e56ce075fec39de9.
Report an issue: GitHub.
Appendix: source
Thrown at src-tauri/src/services/skill.rs:828
let mut downloaded_source: Option<(tempfile::TempDir, PathBuf)> = None;
// 如果已存在则跳过下载
if !dest.exists() {
let repo = SkillRepo {
owner: skill.repo_owner.clone(),
name: skill.repo_name.clone(),
branch: skill.repo_branch.clone(),
enabled: true,
};
// 下载仓库
let (temp_guard, used_branch) = timeout(
std::time::Duration::from_secs(60),
self.download_repo(&repo),
)
.await
.map_err(|_| {
anyhow!(format_skill_error(
"DOWNLOAD_TIMEOUT",
&[
("owner", &repo.owner),
("name", &repo.name),
("timeout", "60")
],
Some("checkNetwork"),
))
})??;
let temp_dir = temp_guard.path();
repo_branch = used_branch;
// 复制到 SSOT
let source =
Self::resolve_skill_source_dir(temp_dir, &skill.directory).ok_or_else(|| {
let missing = temp_dir.join(&source_rel).display().to_string();
anyhow!(format_skill_error(
"SKILL_DIR_NOT_FOUND",View on GitHub (pinned to 06082e189d)