{"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":963,"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 mcode_destination = if skill.apps.mcode {\n                        let destination =\n                            Self::get_distinct_app_skills_dir(&ssot_dir, &AppType::Mcode)?\n                                .join(&directory);","sourceCodeStart":945,"sourceCodeEnd":981,"githubUrl":"https://github.com/farion1231/cc-switch/blob/06082e189d65e6d6dbadc35dacdac1ce6c79d89a/src-tauri/src/services/skill.rs#L945-L981","documentation":"SkillService::uninstall could not find a row in the installed_skills table for the given id (db.get_installed_skill returned None), so it refuses to touch the filesystem. The id no longer exists in the database that is the source of truth.","triggerScenarios":"Calling uninstall_skill_unified twice for the same id (second call fails); passing an id from a stale UI list after another window or session already uninstalled it; using an id captured before a profile/DB sync removed the row.","commonSituations":"Skill list not refreshed after a previous uninstall; two app windows open on the same database; frontend holding cached state after switching profiles or syncing from another machine.","solutions":["Refresh the installed-skills list and retry only if the id is still present","Treat 'Skill not found' during uninstall as already-gone when your goal is an idempotent delete, and simply refresh the UI","Check for a second running instance of the app mutating the same database"],"exampleFix":"// before\nawait invoke('uninstall_skill_unified', { id });\n\n// after: verify existence first, treat missing as success\nconst skills = await invoke<Record<string, { id: string }>>('get_all_installed_skills');\nif (!(id in skills)) {\n  await refreshSkillList(); // already uninstalled elsewhere\n} else {\n  await invoke('uninstall_skill_unified', { id });\n}","handlingStrategy":"validation","validationCode":"const skills = await invoke<Record<string, unknown>>('get_all_installed_skills');\nif (!(id in skills)) {\n  await refreshSkillList(); // nothing to uninstall\n  return;\n}","typeGuard":null,"tryCatchPattern":"try {\n  await invoke('uninstall_skill_unified', { id });\n} catch (e) {\n  if (String(e).startsWith('Skill not found')) {\n    await refreshSkillList(); // treat as already uninstalled\n    return;\n  }\n  throw e;\n}","preventionTips":["Refresh the skill list after every uninstall before enabling further actions","Pass ids taken from the freshly loaded list, never from long-lived caches","Avoid two windows editing skills simultaneously"],"tags":["uninstall","not-found","stale-state","skill-manager"],"backgroundTag":"record-not-found","analyzedSha":"06082e189d65e6d6dbadc35dacdac1ce6c79d89a","analyzedAt":"2026-08-20T14:29:00.113Z","contentChangedAt":"2026-08-20T14:29:00.113Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}