{"record":{"id":"e56ce075fec39de9","repo":"farion1231/cc-switch","slug":"download-timeout","errorCode":"DOWNLOAD_TIMEOUT","errorMessage":"DOWNLOAD_TIMEOUT","messagePattern":"DOWNLOAD_TIMEOUT","errorType":"error_code","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src-tauri/src/services/skill.rs","lineNumber":828,"sourceCode":"        let mut downloaded_source: Option<(tempfile::TempDir, PathBuf)> = None;\n\n        // 如果已存在则跳过下载\n        if !dest.exists() {\n            let repo = SkillRepo {\n                owner: skill.repo_owner.clone(),\n                name: skill.repo_name.clone(),\n                branch: skill.repo_branch.clone(),\n                enabled: true,\n            };\n\n            // 下载仓库\n            let (temp_guard, used_branch) = timeout(\n                std::time::Duration::from_secs(60),\n                self.download_repo(&repo),\n            )\n            .await\n            .map_err(|_| {\n                anyhow!(format_skill_error(\n                    \"DOWNLOAD_TIMEOUT\",\n                    &[\n                        (\"owner\", &repo.owner),\n                        (\"name\", &repo.name),\n                        (\"timeout\", \"60\")\n                    ],\n                    Some(\"checkNetwork\"),\n                ))\n            })??;\n            let temp_dir = temp_guard.path();\n            repo_branch = used_branch;\n\n            // 复制到 SSOT\n            let source =\n                Self::resolve_skill_source_dir(temp_dir, &skill.directory).ok_or_else(|| {\n                    let missing = temp_dir.join(&source_rel).display().to_string();\n                    anyhow!(format_skill_error(\n                        \"SKILL_DIR_NOT_FOUND\",","sourceCodeStart":810,"sourceCodeEnd":846,"githubUrl":"https://github.com/farion1231/cc-switch/blob/06082e189d65e6d6dbadc35dacdac1ce6c79d89a/src-tauri/src/services/skill.rs#L810-L846","documentation":"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.","triggerScenarios":"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.","commonSituations":"Corporate proxies/VPNs slowing GitHub; cloning during CI bandwidth contention; slow first clone of a monorepo that bundles assets; intermittent Wi-Fi.","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)."],"exampleFix":"# before: 300MB skills monorepo over hotel Wi-Fi\ninstall(skill from \"owner/huge-skills-monorepo\")  # -> DOWNLOAD_TIMEOUT owner=owner name=huge-skills-monorepo timeout=60\n\n# after: install from the trimmed single-skill repo\ninstall(skill from \"owner/pdf-tools\")  # clone finishes well under 60s","handlingStrategy":"retry","validationCode":"// Pre-flight reachability of the git host before install\nlet reachable = tokio::net::TcpStream::connect((repo_host.as_str(), 443)).await.is_ok();\nif !reachable { return early_hint(\"checkNetwork\"); }","typeGuard":null,"tryCatchPattern":"let mut attempt = 0;\nloop {\n    match install(&db, &skill, &app).await {\n        Err(e) if e.to_string().contains(\"DOWNLOAD_TIMEOUT\") && attempt < 2 => { attempt += 1; tokio::time::sleep(Duration::from_secs(5)).await; continue; }\n        other => break other?,\n    }\n}","preventionTips":["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."],"tags":["skills","network","timeout","download","tauri"],"backgroundTag":"download-request-timeout","analyzedSha":"06082e189d65e6d6dbadc35dacdac1ce6c79d89a","analyzedAt":"2026-08-20T14:29:00.113Z","contentChangedAt":"2026-08-20T14:29:00.113Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}