{"record":{"id":"714be049aa16eca8","repo":"farion1231/cc-switch","slug":"skill-backup-is-invalid-or-missing-skill-md","errorCode":null,"errorMessage":"Skill backup is invalid or missing SKILL.md: {}","messagePattern":"Skill backup is invalid or missing SKILL\\.md: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src-tauri/src/services/skill.rs","lineNumber":1768,"sourceCode":"\n        fs::remove_dir_all(&backup_path)\n            .with_context(|| format!(\"failed to delete {}\", backup_path.display()))?;\n\n        log::info!(\"Skill 备份已删除: {}\", backup_path.display());\n        Ok(())\n    }\n\n    pub fn restore_from_backup(\n        db: &Arc<Database>,\n        backup_id: &str,\n        current_app: &AppType,\n    ) -> Result<InstalledSkill> {\n        let _state_guard = skill_state_write_guard();\n        let backup_path = Self::backup_path_for_id(backup_id)?;\n        let metadata = Self::read_backup_metadata(&backup_path)?;\n        let backup_skill_dir = backup_path.join(\"skill\");\n        if !backup_skill_dir.join(\"SKILL.md\").exists() {\n            return Err(anyhow!(\n                \"Skill backup is invalid or missing SKILL.md: {}\",\n                backup_path.display()\n            ));\n        }\n\n        let existing_skills = db.get_all_installed_skills()?;\n        if existing_skills.contains_key(&metadata.skill.id)\n            || existing_skills.values().any(|skill| {\n                skill\n                    .directory\n                    .eq_ignore_ascii_case(&metadata.skill.directory)\n            })\n        {\n            return Err(anyhow!(\n                \"Skill already exists, please uninstall the current one first: {}\",\n                metadata.skill.directory\n            ));\n        }","sourceCodeStart":1750,"sourceCodeEnd":1786,"githubUrl":"https://github.com/farion1231/cc-switch/blob/0b5da510168914b251481654a568c3ffacd62cf4/src-tauri/src/services/skill.rs#L1750-L1786","documentation":"restore_from_backup requires backup_path/skill/SKILL.md to exist; SKILL.md is the defining artifact of a skill. Its absence means the backup is incomplete, corrupted, or was assembled by hand without the payload directory. meta.json parsed fine, but there is nothing restorable.","triggerScenarios":"Backup creation interrupted (app killed, disk full) after meta.json was written but before the skill files were copied; files inside the backup deleted afterwards; a backup folder recreated manually with only meta.json; antivirus quarantining SKILL.md.","commonSituations":"Crash during uninstall-backup; users pruning 'large files' inside backups; backups copied between machines with partial transfer.","solutions":["Open the backup folder shown in the error and verify the layout: meta.json plus skill/SKILL.md","If skill/ is missing or gutted, use a different backup or accept the skill is unrecoverable from this backup","Delete the broken backup entry (delete_skill_backup) to clean the list, and re-create backups from live installs before uninstalling in the future"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const backups = await invoke<Array<{ id: string }>>('list_skill_backups');\n// Prefer offering restore only for backups you did not hand-modify;\n// verify the entry still exists and is recent before invoking restore.\nif (!backups.some(b => b.id === backupId)) {\n  notify('Backup missing — refresh the backup list');\n  return;\n}","typeGuard":null,"tryCatchPattern":"try {\n  await invoke('restore_skill_backup', { backupId, currentApp });\n} catch (e) {\n  if (String(e).includes('missing SKILL.md')) {\n    notify('Backup is incomplete — use another backup or delete this entry');\n    return;\n  }\n  throw e;\n}","preventionTips":["Let uninstall backups finish — do not kill the app mid-uninstall","Never prune files inside backup folders","Delete broken backup entries via delete_skill_backup to keep the list clean"],"tags":["backup","restore","validation","corruption","skill-manager"],"backgroundTag":"backup-validation-failed","analyzedSha":"0b5da510168914b251481654a568c3ffacd62cf4","analyzedAt":"2026-08-20T14:29:00.113Z","contentChangedAt":"2026-08-20T14:29:00.113Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}