{"record":{"id":"10183ff0ed4e07fd","repo":"farion1231/cc-switch","slug":"skill-not-found-id","errorCode":null,"errorMessage":"Skill not found: {id}","messagePattern":"Skill not found: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src-tauri/src/services/skill.rs","lineNumber":962,"sourceCode":"            current_app\n        );\n\n        Ok(installed_skill)\n    }\n\n    /// 卸载 Skill\n    ///\n    /// 流程：\n    /// 1. 从所有应用目录删除\n    /// 2. 从 SSOT 删除\n    /// 3. 从数据库删除\n    pub fn uninstall(db: &Arc<Database>, id: &str) -> Result<SkillUninstallResult> {\n        let _state_guard = skill_state_write_guard();\n\n        // 获取 skill 信息\n        let skill = db\n            .get_installed_skill(id)?\n            .ok_or_else(|| anyhow!(\"Skill not found: {id}\"))?;\n\n        // DB 行可能被同步导入污染（远端快照 raw SQL 直接灌库，绕过安装期校验），\n        // 也可能是 v3.11.0 引入 sanitize_install_name 之前留下的存量脏值\n        // （当年扫描不过滤点开头目录，`.github/SKILL.md` 会存成 `.github`）。\n        //\n        // 守卫失败时**跳过全部文件系统操作、但仍删除 DB 行**：`db.delete_skill`\n        // 全项目只有这一处调用且未暴露为命令，若在此直接返回 Err，用户就再也无法\n        // 从界面删掉这条记录，只能手改 SQLite。安全目标是「不碰危险路径」，\n        // 不是「把用户锁在坏状态里」。\n        let (backup_path, preserved_pi_path, pi_cleanup_incomplete) =\n            match Self::require_valid_directory(&skill.directory) {\n                Ok(directory) => {\n                    let ssot_dir = Self::get_ssot_dir()?;\n                    let source = ssot_dir.join(&directory);\n                    let mut preserved_pi_path: Option<PathBuf> = None;\n                    let mut pi_cleanup_incomplete = false;\n                    let mut pi_removal_path = None;\n","sourceCodeStart":944,"sourceCodeEnd":980,"githubUrl":"https://github.com/farion1231/cc-switch/blob/a2e22f330273a5b6ffa87cb8b82b624601bac562/src-tauri/src/services/skill.rs#L944-L980","documentation":"Uninstall looks up the skill row by id in SQLite via db.get_installed_skill before touching the filesystem; when the query returns None there is nothing to uninstall and the error is raised. It is a plain not-found guard at the top of SkillService::uninstall, before any directory is validated or deleted.","triggerScenarios":"Calling the uninstall command with an id that is not in the installed_skills table: the skill was already uninstalled from another window/device, the UI list is stale after a cloud-sync import replaced rows, or the id string is malformed/truncated.","commonSituations":"Stale list rendered before a background sync import wiped and re-inserted rows; double-click on uninstall where the first call already removed the row; passing a marketplace skill id instead of the installed skill id.","solutions":["Refresh the installed-skills list and re-render before retrying","Confirm you are passing the installed skill's id (InstalledSkill.id), not the marketplace/repo identifier","If the row keeps disappearing, check cloud-sync import logs — a remote snapshot may be deleting rows under you"],"exampleFix":"// before\nawait invoke('uninstall_skill', { id });\n\n// after\nconst skills = await invoke('list_installed_skills');\nif (!skills.some(s => s.id === id)) {\n  await refreshSkillList(); // stale view — nothing to uninstall\n} else {\n  await invoke('uninstall_skill', { id });\n}","handlingStrategy":"validation","validationCode":"const skills = await invoke('list_installed_skills');\nif (!skills.some(s => s.id === id)) {\n  await refreshSkillList();\n  return; // nothing to uninstall\n}","typeGuard":"const isInstalledSkill = (s: unknown, id: string): s is InstalledSkill =>\n  typeof s === 'object' && s !== null && (s as any).id === id;","tryCatchPattern":"try { await invoke('uninstall_skill', { id }); }\ncatch (e) {\n  if (String(e).includes('Skill not found')) { await refreshSkillList(); return; }\n  throw e;\n}","preventionTips":["Always render uninstall actions from a freshly fetched installed list","Pass InstalledSkill.id, never marketplace identifiers"],"tags":["rust","tauri","sqlite","not-found","uninstall"],"backgroundTag":null,"analyzedSha":"a2e22f330273a5b6ffa87cb8b82b624601bac562","analyzedAt":"2026-08-16T03:46:07.889Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}