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
- Inspect the ZIP: unzip -l skills.zip — confirm some directory contains a file named exactly SKILL.md
- Re-create the ZIP so it contains <skill-name>/SKILL.md at or below the root
- 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
- Always zip the skill folder itself so <name>/SKILL.md is inside
- Check the manifest filename casing — the scanner matches SKILL.md exactly
- Run unzip -l on exported ZIPs as a packaging sanity check
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
- TOML 内容不能为空
- Invalid skill directory (possible path traversal): {director
- OPENCLAW_ENV_EMPTY
- OPENCLAW_ENV_INVALID_JSON
- Unsupported URL scheme
AI-assisted analysis of farion1231/cc-switch@a2e22f3302 (2026-08-16).
Data as JSON: /api/errors/85db41bbd21cc10d.
Report an issue: GitHub.