farion1231/cc-switch · error · anyhow::Error
Cannot update local skill: {skill_id}
Error message
Cannot update local skill: {skill_id} What it means
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.
Source
Thrown at src-tauri/src/services/skill.rs:1380
.get_installed_skill(skill_id)?
.ok_or_else(|| anyhow!("Skill not found: {skill_id}"))?;
skill.apps.pi = Self::skill_exists_in_app(&skill.directory, &AppType::Pi);
// 本函数后续三种危险操作都用 directory 拼路径:备份源(把任意目录复制进
// 备份区并在界面列出)、remove_dir_all(删任意目录)、copy_dir_recursive
// (把远端仓库内容写到任意路径)。校验必须在这三者之前。
Self::require_valid_directory(&skill.directory)?;
let (owner, name, branch) = match (&skill.repo_owner, &skill.repo_name) {
(Some(o), Some(n)) => (
o.clone(),
n.clone(),
skill
.repo_branch
.clone()
.unwrap_or_else(|| "main".to_string()),
),
_ => return Err(anyhow!("Cannot update local skill: {skill_id}")),
};
let repo = SkillRepo {
owner: owner.clone(),
name: name.clone(),
branch: branch.clone(),
enabled: true,
};
let ssot_dir = Self::get_ssot_dir()?;
if skill.apps.pi {
Self::get_distinct_app_skills_dir(&ssot_dir, &AppType::Pi)?;
}
// 下载仓库
let (temp_guard, used_branch) = timeout(
std::time::Duration::from_secs(60),
self.download_repo(&repo),View on GitHub (pinned to 0b5da51016)
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
Example fix
// before
const canUpdate = (s: InstalledSkill) => true;
// after: only repo-backed skills are updatable
const canUpdate = (s: InstalledSkill) =>
s.repo_owner != null && s.repo_name != null;
if (canUpdate(skill)) await invoke('update_skill', { id: skill.id }); Defensive patterns
Strategy: type-guard
Type guard
interface RepoSkill extends InstalledSkill { repo_owner: string; repo_name: string }
const isRepoSkill = (s: InstalledSkill): s is RepoSkill =>
s.repo_owner != null && s.repo_name != null;
// usage
if (isRepoSkill(skill)) await invoke('update_skill', { id: skill.id });
else hideUpdateButton(skill); Try / catch
try {
await invoke('update_skill', { id });
} catch (e) {
if (String(e).includes('Cannot update local skill')) {
notify('Local skills cannot be updated — edit files or reinstall from a repo');
return;
}
throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Skill no longer installed: {}
- Skill not found: {skill_id}
- Skill changed during update: {}
- Skill directory changed during install; please retry
- Skill not found: {id}
AI-assisted analysis of farion1231/cc-switch@0b5da51016 (2026-08-20).
Data as JSON: /api/errors/68abc77d5a92eff7.
Report an issue: GitHub.