farion1231/cc-switch · error

EMPTY_ARCHIVE

EMPTY_ARCHIVE

Error message

EMPTY_ARCHIVE

What it means

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.

Source

Thrown at src-tauri/src/services/skill.rs:3253

            )));
        }
        *total_bytes += amount;
        Ok(())
    }

    /// 把 GitHub 仓库归档解压到 `dest`(剥掉归档自带的一层根目录)。
    ///
    /// 与 `download_and_extract` 分离,使 zip-slip 防护可在不联网的情况下被单测覆盖。
    fn extract_repo_archive<R: std::io::Read + std::io::Seek>(
        mut archive: zip::ZipArchive<R>,
        dest: &Path,
    ) -> Result<()> {
        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"),
            )));
        }

View on GitHub (pinned to a2e22f3302)

Solutions

  1. Retry once — transient archive-service glitches do occur
  2. Try the repo's main branch explicitly (or clear the branch field) in case the named ref is in a broken state
  3. Verify https://github.com/{owner}/{name}/archive/refs/heads/{branch}.zip in a browser to see what is actually served
Defensive patterns

Strategy: validation

Validate before calling

// Rust — pre-flight: confirm the branch ref exists before discovery
async fn branch_exists(owner: &str, name: &str, branch: &str) -> Result<bool> {
    let url = format!("https://api.github.com/repos/{owner}/{name}/branches/{branch}");
    let resp = crate::proxy::http_client::get().get(url).send().await?;
    Ok(resp.status().is_success())
}

Type guard

export function isEmptyArchive(e: unknown): boolean {
  return typeof e === "string" && e.includes('"code":"EMPTY_ARCHIVE"');
}

Try / catch

match fetch_repo_skills(&repo).await {
    Err(e) if e.to_string().contains("EMPTY_ARCHIVE") => {
        // one retry with branch cleared (forces main/master), then give up with checkRepoUrl hint
    }
    other => other,
}

Prevention

When it happens

Trigger: 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.

Common situations: 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).

Related errors


AI-assisted analysis of farion1231/cc-switch@a2e22f3302 (2026-08-16). Data as JSON: /api/errors/17548cf6cddbf593. Report an issue: GitHub.