farion1231/cc-switch · error

NO_SKILLS_IN_ZIP

NO_SKILLS_IN_ZIP

Error message

NO_SKILLS_IN_ZIP

What it means

Structured error from install_from_zip: the local ZIP was extracted successfully, but scan_skills_in_dir found no subdirectory containing a SKILL.md. The installer only knows how to install directories that look like skills, so an archive with no manifest anywhere is rejected before anything is written to SSOT.

Source

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

    /// 流程:
    /// 1. 解压 ZIP 到临时目录
    /// 2. 扫描目录查找包含 SKILL.md 的技能
    /// 3. 复制到 SSOT 并保存到数据库
    /// 4. 同步到当前应用目录
    pub fn install_from_zip(
        db: &Arc<Database>,
        zip_path: &Path,
        current_app: &AppType,
    ) -> Result<Vec<InstalledSkill>> {
        // 解压到临时目录
        let temp_guard = Self::extract_local_zip(zip_path)?;
        let temp_dir = temp_guard.path();

        // 扫描所有包含 SKILL.md 的目录
        let skill_dirs = Self::scan_skills_in_dir(temp_dir)?;

        if skill_dirs.is_empty() {
            return Err(anyhow!(format_skill_error(
                "NO_SKILLS_IN_ZIP",
                &[],
                Some("checkZipContent"),
            )));
        }

        let _state_guard = skill_state_write_guard();
        let ssot_dir = Self::get_ssot_dir()?;
        let mut installed = Vec::new();
        let existing_skills = db.get_all_installed_skills()?;
        let zip_stem = zip_path
            .file_stem()
            .and_then(|s| s.to_str())
            .map(|s| s.to_string());

        for skill_dir in skill_dirs {
            // 解析元数据(提前解析,用于确定安装名)
            let skill_md = skill_dir.join("SKILL.md");

View on GitHub (pinned to a2e22f3302)

Solutions

  1. Inspect the ZIP: unzip -l skills.zip — confirm some directory contains a file named exactly SKILL.md
  2. Re-create the ZIP so it contains <skill-name>/SKILL.md at or below the root
  3. Fix the manifest filename case/spelling in the source folder and re-zip

Example fix

# before
zip -r skills.zip . -x '*.git*'   # may exclude or misplace SKILL.md

# after — zip the skill folder itself so <name>/SKILL.md is present
zip -r skills.zip my-skill/
Defensive patterns

Strategy: validation

Validate before calling

// Rust — pre-scan a ZIP for a SKILL.md before invoking install_from_zip
fn zip_has_skill_manifest(zip_path: &Path) -> Result<bool> {
    let file = fs::File::open(zip_path)?;
    let mut zip = zip::ZipArchive::new(file)?;
    Ok((0..zip.len()).any(|i| {
        zip.by_index(i).map(|e| e.name().ends_with("SKILL.md")).unwrap_or(false)
    }))
}

Type guard

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

Try / catch

match install_from_zip(&db, &zip, &app).await {
    Err(e) if e.to_string().contains("NO_SKILLS_IN_ZIP") => {
        // show 'the ZIP contains no skill (missing SKILL.md)' and keep the file picker open
    }
    other => other,
}

Prevention

When it happens

Trigger: Importing a ZIP that is not a skill package: documentation archives, a ZIP of loose files with SKILL.md missing, a folder where the manifest is misspelled (skill.md / SKILL.MD — the scan is for the exact name SKILL.md), or the skill folder was excluded when the ZIP was created.

Common situations: Users right-click 'Compress' on the wrong folder; macOS/Windows tools skipping dotfiles or specific files; manifest renamed while editing; zipping the repo root of a project whose skills live deeper than expected but with different naming.

Related errors


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