{"record":{"id":"68abc77d5a92eff7","repo":"farion1231/cc-switch","slug":"cannot-update-local-skill-skill-id","errorCode":null,"errorMessage":"Cannot update local skill: {skill_id}","messagePattern":"Cannot update local skill: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src-tauri/src/services/skill.rs","lineNumber":1380,"sourceCode":"            .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        };\n\n        let repo = SkillRepo {\n            owner: owner.clone(),\n            name: name.clone(),\n            branch: branch.clone(),\n            enabled: true,\n        };\n\n        let ssot_dir = Self::get_ssot_dir()?;\n        if skill.apps.pi {\n            Self::get_distinct_app_skills_dir(&ssot_dir, &AppType::Pi)?;\n        }\n\n        // 下载仓库\n        let (temp_guard, used_branch) = timeout(\n            std::time::Duration::from_secs(60),\n            self.download_repo(&repo),","sourceCodeStart":1362,"sourceCodeEnd":1398,"githubUrl":"https://github.com/farion1231/cc-switch/blob/0b5da510168914b251481654a568c3ffacd62cf4/src-tauri/src/services/skill.rs#L1362-L1398","documentation":"update_skill needs repo_owner and repo_name to re-download a skill; for locally imported skills (manual folder import, zip import, or rows synced without repo fields) both columns are NULL. The match arm falls through and the service refuses to 'update' a skill it has no upstream for.","triggerScenarios":"Calling update_skill (or 'update all') on a skill created via local/zip import whose repo_owner/repo_name are NULL; restoring an old backup whose metadata lacked repo coordinates.","commonSituations":"Update-all buttons that don't filter local skills; users expecting local skills to refresh from somewhere; migrated databases where repo fields were never populated.","solutions":["Filter local skills (repo_owner is null) out of update/update-all flows in the UI","To track upstream, uninstall the local skill and install it from its GitHub repo","For local skills, edit the files in the SSOT directory directly — there is nothing to download"],"exampleFix":"// before\nconst canUpdate = (s: InstalledSkill) => true;\n\n// after: only repo-backed skills are updatable\nconst canUpdate = (s: InstalledSkill) =>\n  s.repo_owner != null && s.repo_name != null;\n\nif (canUpdate(skill)) await invoke('update_skill', { id: skill.id });","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"interface RepoSkill extends InstalledSkill { repo_owner: string; repo_name: string }\nconst isRepoSkill = (s: InstalledSkill): s is RepoSkill =>\n  s.repo_owner != null && s.repo_name != null;\n\n// usage\nif (isRepoSkill(skill)) await invoke('update_skill', { id: skill.id });\nelse hideUpdateButton(skill);","tryCatchPattern":"try {\n  await invoke('update_skill', { id });\n} catch (e) {\n  if (String(e).includes('Cannot update local skill')) {\n    notify('Local skills cannot be updated — edit files or reinstall from a repo');\n    return;\n  }\n  throw e;\n}","preventionTips":["Hide update actions for skills with null repo_owner/repo_name","Filter local skills out of update-all batches","Install from a GitHub repo when you want update support"],"tags":["local-skill","update","unsupported-operation","skill-manager"],"backgroundTag":"unsupported-operation-for-resource-type","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"}