{"record":{"id":"17548cf6cddbf593","repo":"farion1231/cc-switch","slug":"empty-archive","errorCode":"EMPTY_ARCHIVE","errorMessage":"EMPTY_ARCHIVE","messagePattern":"EMPTY_ARCHIVE","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src-tauri/src/services/skill.rs","lineNumber":3253,"sourceCode":"            )));\n        }\n        *total_bytes += amount;\n        Ok(())\n    }\n\n    /// 把 GitHub 仓库归档解压到 `dest`（剥掉归档自带的一层根目录）。\n    ///\n    /// 与 `download_and_extract` 分离，使 zip-slip 防护可在不联网的情况下被单测覆盖。\n    fn extract_repo_archive<R: std::io::Read + std::io::Seek>(\n        mut archive: zip::ZipArchive<R>,\n        dest: &Path,\n    ) -> Result<()> {\n        let root_name = if !archive.is_empty() {\n            let first_file = archive.by_index(0)?;\n            let name = first_file.name();\n            name.split('/').next().unwrap_or(\"\").to_string()\n        } else {\n            return Err(anyhow::anyhow!(format_skill_error(\n                \"EMPTY_ARCHIVE\",\n                &[],\n                Some(\"checkRepoUrl\"),\n            )));\n        };\n\n        // 归档字节完全由第三方控制（仓库可经 deeplink 添加），所以解压必须限量，\n        // 否则一个几 MB 的压缩炸弹就能塞满磁盘。webdav_sync/archive.rs 早有同款\n        // 双重上限，这条下载路径一直没有。\n        if archive.len() > MAX_ARCHIVE_ENTRIES {\n            let count = archive.len().to_string();\n            let limit = MAX_ARCHIVE_ENTRIES.to_string();\n            return Err(anyhow::anyhow!(format_skill_error(\n                \"ARCHIVE_TOO_MANY_ENTRIES\",\n                &[(\"count\", &count), (\"limit\", &limit)],\n                Some(\"checkZipContent\"),\n            )));\n        }","sourceCodeStart":3235,"sourceCodeEnd":3271,"githubUrl":"https://github.com/farion1231/cc-switch/blob/a2e22f330273a5b6ffa87cb8b82b624601bac562/src-tauri/src/services/skill.rs#L3235-L3271","documentation":"Structured error from extract_repo_archive: the remote ZIP opened successfully but contains zero entries. Real GitHub branch archives always contain at least the repo-root directory entry, so an empty-but-valid ZIP means the downloaded bytes are not what the endpoint normally produces.","triggerScenarios":"The archive URL returned an empty (but structurally valid) ZIP — e.g. a proxy/mirror replacing the body, an edge case in GitHub's archive service, or a truncated download that still parses as an empty archive.","commonSituations":"Rare; usually paired with network middleboxes or a repo in a broken state. Distinct from a 404 (that fails earlier with DOWNLOAD_FAILED) and from a corrupt body (ZipArchive::new fails before this check).","solutions":["Retry once — transient archive-service glitches do occur","Try the repo's main branch explicitly (or clear the branch field) in case the named ref is in a broken state","Verify https://github.com/{owner}/{name}/archive/refs/heads/{branch}.zip in a browser to see what is actually served"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Rust — pre-flight: confirm the branch ref exists before discovery\nasync fn branch_exists(owner: &str, name: &str, branch: &str) -> Result<bool> {\n    let url = format!(\"https://api.github.com/repos/{owner}/{name}/branches/{branch}\");\n    let resp = crate::proxy::http_client::get().get(url).send().await?;\n    Ok(resp.status().is_success())\n}","typeGuard":"export function isEmptyArchive(e: unknown): boolean {\n  return typeof e === \"string\" && e.includes('\"code\":\"EMPTY_ARCHIVE\"');\n}","tryCatchPattern":"match fetch_repo_skills(&repo).await {\n    Err(e) if e.to_string().contains(\"EMPTY_ARCHIVE\") => {\n        // one retry with branch cleared (forces main/master), then give up with checkRepoUrl hint\n    }\n    other => other,\n}","preventionTips":["An empty-but-valid ZIP almost always means a middlebox or archive-service anomaly — verify the archive URL in a browser","Prefer the default branch when adding repos to minimize exotic-ref edge cases"],"tags":["archive","zip","github","download"],"backgroundTag":null,"analyzedSha":"a2e22f330273a5b6ffa87cb8b82b624601bac562","analyzedAt":"2026-08-16T03:46:07.889Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}