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

  1. Filter local skills (repo_owner is null) out of update/update-all flows in the UI
  2. To track upstream, uninstall the local skill and install it from its GitHub repo
  3. 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

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


AI-assisted analysis of farion1231/cc-switch@0b5da51016 (2026-08-20). Data as JSON: /api/errors/68abc77d5a92eff7. Report an issue: GitHub.