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

  1. Point the app at a lean repo containing only the skills you need
  2. 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
  3. 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

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.