{"record":{"id":"d0e500587817f003","repo":"farion1231/cc-switch","slug":"archive-too-many-entries","errorCode":"ARCHIVE_TOO_MANY_ENTRIES","errorMessage":"ARCHIVE_TOO_MANY_ENTRIES","messagePattern":"ARCHIVE_TOO_MANY_ENTRIES","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src-tauri/src/services/skill.rs","lineNumber":3266,"sourceCode":"        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        }\n        let mut total_bytes: u64 = 0;\n\n        // 第一遍：解压普通文件和目录，收集 symlink 条目\n        let mut symlinks: Vec<(PathBuf, String)> = Vec::new();\n\n        for i in 0..archive.len() {\n            let mut file = archive.by_index(i)?;\n            // 第一道：enclosed_name() 拒绝绝对路径、盘符前缀，以及净深度为负\n            // （即逃出归档自身根目录）的条目。skill 仓库可由 deeplink 添加，\n            // 压缩包内容属第三方可控输入。\n            let Some(safe_path) = file.enclosed_name() else {\n                log::warn!(\"跳过不安全的压缩包条目: {}\", file.name());\n                continue;","sourceCodeStart":3248,"sourceCodeEnd":3284,"githubUrl":"https://github.com/farion1231/cc-switch/blob/a2e22f330273a5b6ffa87cb8b82b624601bac562/src-tauri/src/services/skill.rs#L3248-L3284","documentation":"Structured error from extract_repo_archive: the remote archive has more than MAX_ARCHIVE_ENTRIES (10,000, skill.rs:314) entries, checked before any extraction begins. Same style of cap as webdav_sync/archive.rs; it bounds inode/dirblock consumption from third-party-controlled archives and makes entry-count bombs fail fast.","triggerScenarios":"Discovering or installing a repo with >10k files — monorepos, repos with generated code, node_modules-style checkins, or a crafted archive stuffed with entries.","commonSituations":"Users adding a large multi-project repo as a 'skill repo'; hostile deeplink repos. Legit skill collections are far below 10k files.","solutions":["Point the app at a lean repo containing only the skills you need","Create a ZIP with just the skill directory and import it locally — the local path enforces the same 10k cap, so trim the tree first","If maintaining the repo, prune generated/vendored files below the limit"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Rust — pre-flight entry-count estimate via git trees API (recursive)\nasync fn entry_count(owner: &str, name: &str, branch: &str) -> Result<Option<u64>> {\n    let url = format!(\"https://api.github.com/repos/{owner}/{name}/git/trees/{branch}?recursive=1\");\n    let resp: serde_json::Value = crate::proxy::http_client::get().get(url).send().await?.json().await?;\n    Ok(resp[\"tree\"].as_array().map(|a| a.len() as u64))\n}","typeGuard":"export function isTooManyEntries(e: unknown): boolean {\n  return typeof e === \"string\" && e.includes('\"code\":\"ARCHIVE_TOO_MANY_ENTRIES\"');\n}","tryCatchPattern":"match download_repo(&repo).await {\n    Err(e) if e.to_string().contains(\"ARCHIVE_TOO_MANY_ENTRIES\") => {\n        // permanent for this repo: suggest a trimmed repo; never retry the same URL\n    }\n    other => other,\n}","preventionTips":["Keep skill repos under a few thousand files — the 10,000 cap is generous for markdown skills","Prune generated code, vendored deps, and media before publishing a skill repo"],"tags":["archive","limits","security","github"],"backgroundTag":null,"analyzedSha":"a2e22f330273a5b6ffa87cb8b82b624601bac562","analyzedAt":"2026-08-16T03:46:07.889Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}