farion1231/cc-switch · error
ARCHIVE_TOO_MANY_ENTRIES
ARCHIVE_TOO_MANY_ENTRIES
Error message
ARCHIVE_TOO_MANY_ENTRIES
What it means
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.
Source
Thrown at src-tauri/src/services/skill.rs:3266
let root_name = if !archive.is_empty() {
let first_file = archive.by_index(0)?;
let name = first_file.name();
name.split('/').next().unwrap_or("").to_string()
} else {
return Err(anyhow::anyhow!(format_skill_error(
"EMPTY_ARCHIVE",
&[],
Some("checkRepoUrl"),
)));
};
// 归档字节完全由第三方控制(仓库可经 deeplink 添加),所以解压必须限量,
// 否则一个几 MB 的压缩炸弹就能塞满磁盘。webdav_sync/archive.rs 早有同款
// 双重上限,这条下载路径一直没有。
if archive.len() > MAX_ARCHIVE_ENTRIES {
let count = archive.len().to_string();
let limit = MAX_ARCHIVE_ENTRIES.to_string();
return Err(anyhow::anyhow!(format_skill_error(
"ARCHIVE_TOO_MANY_ENTRIES",
&[("count", &count), ("limit", &limit)],
Some("checkZipContent"),
)));
}
let mut total_bytes: u64 = 0;
// 第一遍:解压普通文件和目录,收集 symlink 条目
let mut symlinks: Vec<(PathBuf, String)> = Vec::new();
for i in 0..archive.len() {
let mut file = archive.by_index(i)?;
// 第一道:enclosed_name() 拒绝绝对路径、盘符前缀,以及净深度为负
// (即逃出归档自身根目录)的条目。skill 仓库可由 deeplink 添加,
// 压缩包内容属第三方可控输入。
let Some(safe_path) = file.enclosed_name() else {
log::warn!("跳过不安全的压缩包条目: {}", file.name());
continue;View on GitHub (pinned to a2e22f3302)
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
Defensive patterns
Strategy: validation
Validate before calling
// Rust — pre-flight entry-count estimate via git trees API (recursive)
async fn entry_count(owner: &str, name: &str, branch: &str) -> Result<Option<u64>> {
let url = format!("https://api.github.com/repos/{owner}/{name}/git/trees/{branch}?recursive=1");
let resp: serde_json::Value = crate::proxy::http_client::get().get(url).send().await?.json().await?;
Ok(resp["tree"].as_array().map(|a| a.len() as u64))
} Type guard
export function isTooManyEntries(e: unknown): boolean {
return typeof e === "string" && e.includes('"code":"ARCHIVE_TOO_MANY_ENTRIES"');
} Try / catch
match download_repo(&repo).await {
Err(e) if e.to_string().contains("ARCHIVE_TOO_MANY_ENTRIES") => {
// permanent for this repo: suggest a trimmed repo; never retry the same URL
}
other => other,
} Prevention
- 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
When it happens
Trigger: Discovering or installing a repo with >10k files — monorepos, repos with generated code, node_modules-style checkins, or a crafted archive stuffed with entries.
Common situations: Users adding a large multi-project repo as a 'skill repo'; hostile deeplink repos. Legit skill collections are far below 10k files.
Related errors
AI-assisted analysis of farion1231/cc-switch@a2e22f3302 (2026-08-16).
Data as JSON: /api/errors/d0e500587817f003.
Report an issue: GitHub.