{"record":{"id":"773cb653cda5b07b","repo":"farion1231/cc-switch","slug":"skill-not-found-skill-id","errorCode":null,"errorMessage":"Skill not found: {skill_id}","messagePattern":"Skill not found: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src-tauri/src/services/skill.rs","lineNumber":1363,"sourceCode":"    /// 使用只更新现有记录的 DAO，避免旧快照覆盖 `enabled_*`，也避免已卸载记录\n    /// 被重新插入。\n    fn persist_updated_skill_metadata(\n        db: &Arc<Database>,\n        updated_skill: &InstalledSkill,\n    ) -> Result<InstalledSkill> {\n        if !db.update_skill_metadata(updated_skill)? {\n            return Err(anyhow!(\"Skill no longer installed: {}\", updated_skill.id));\n        }\n\n        db.get_installed_skill(&updated_skill.id)?\n            .ok_or_else(|| anyhow!(\"Skill no longer installed: {}\", updated_skill.id))\n    }\n\n    /// 更新单个 Skill（重新下载并替换本地文件）\n    pub async fn update_skill(&self, db: &Arc<Database>, skill_id: &str) -> Result<InstalledSkill> {\n        let mut skill = db\n            .get_installed_skill(skill_id)?\n            .ok_or_else(|| anyhow!(\"Skill not found: {skill_id}\"))?;\n        skill.apps.pi = Self::skill_exists_in_app(&skill.directory, &AppType::Pi);\n\n        // 本函数后续三种危险操作都用 directory 拼路径：备份源（把任意目录复制进\n        // 备份区并在界面列出）、remove_dir_all（删任意目录）、copy_dir_recursive\n        // （把远端仓库内容写到任意路径）。校验必须在这三者之前。\n        Self::require_valid_directory(&skill.directory)?;\n\n        let (owner, name, branch) = match (&skill.repo_owner, &skill.repo_name) {\n            (Some(o), Some(n)) => (\n                o.clone(),\n                n.clone(),\n                skill\n                    .repo_branch\n                    .clone()\n                    .unwrap_or_else(|| \"main\".to_string()),\n            ),\n            _ => return Err(anyhow!(\"Cannot update local skill: {skill_id}\")),\n        };","sourceCodeStart":1345,"sourceCodeEnd":1381,"githubUrl":"https://github.com/farion1231/cc-switch/blob/0b5da510168914b251481654a568c3ffacd62cf4/src-tauri/src/services/skill.rs#L1345-L1381","documentation":"Entry lookup of SkillService::update_skill: the installed_skills table has no row with the given skill_id, so the update cannot even start. Unlike errors 42/43/49 this is not a mid-flight race — the id was already stale when the call was made.","triggerScenarios":"Clicking Update on a skill listed from a stale cache after it was uninstalled elsewhere; passing a wrong or outdated id to the update_skill command; 'update all' iterating a list captured before some rows were removed.","commonSituations":"UI list not refreshed after uninstall; duplicate windows on one database; batch update loops built from a snapshot.","solutions":["Re-fetch the installed skills list and only offer Update for ids that still exist","Skip (not fail) missing ids in batch update loops","Refresh after any uninstall before enabling update actions"],"exampleFix":"// before\nfor (const s of cachedSkills) await invoke('update_skill', { id: s.id });\n\n// after: re-read the source of truth, skip gone entries\nconst current = await invoke<Record<string, { id: string }>>('get_all_installed_skills');\nfor (const id of Object.keys(current)) {\n  await invoke('update_skill', { id });\n}","handlingStrategy":"validation","validationCode":"const skills = await invoke<Record<string, unknown>>('get_all_installed_skills');\nif (!(id in skills)) {\n  await refreshSkillList();\n  return;\n}\nawait invoke('update_skill', { id });","typeGuard":null,"tryCatchPattern":"try {\n  await invoke('update_skill', { id });\n} catch (e) {\n  if (String(e).startsWith('Skill not found')) {\n    await refreshSkillList();\n    return;\n  }\n  throw e;\n}","preventionTips":["Build update-all loops from a freshly fetched list, not a cached snapshot","Skip missing ids in batch loops instead of aborting the batch","Refresh after uninstall/profile-switch before offering updates"],"tags":["update","not-found","stale-state","skill-manager"],"backgroundTag":"record-not-found","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"}