farion1231/cc-switch · error · anyhow::Error

Skill not found

Error message

Skill not found: {id}

What it means

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.

Solutions

  1. Refresh the installed-skills list and retry only if the id is still present
  2. Treat 'Skill not found' during uninstall as already-gone when your goal is an idempotent delete, and simply refresh the UI
  3. Check for a second running instance of the app mutating the same database

Example fix

// before
await invoke('uninstall_skill_unified', { id });

// after: verify existence first, treat missing as success
const skills = await invoke<Record<string, { id: string }>>('get_all_installed_skills');
if (!(id in skills)) {
  await refreshSkillList(); // already uninstalled elsewhere
} else {
  await invoke('uninstall_skill_unified', { id });
}
Defensive patterns

Strategy: validation

Validate before calling

const skills = await invoke<Record<string, unknown>>('get_all_installed_skills');
if (!(id in skills)) {
  await refreshSkillList(); // nothing to uninstall
  return;
}

Try / catch

try {
  await invoke('uninstall_skill_unified', { id });
} catch (e) {
  if (String(e).startsWith('Skill not found')) {
    await refreshSkillList(); // treat as already uninstalled
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of farion1231/cc-switch@06082e189d (2026-08-20). Data as JSON: /api/errors/10183ff0ed4e07fd. Report an issue: GitHub.

Appendix: source

Thrown at src-tauri/src/services/skill.rs:963

            current_app
        );

        Ok(installed_skill)
    }

    /// 卸载 Skill
    ///
    /// 流程:
    /// 1. 从所有应用目录删除
    /// 2. 从 SSOT 删除
    /// 3. 从数据库删除
    pub fn uninstall(db: &Arc<Database>, id: &str) -> Result<SkillUninstallResult> {
        let _state_guard = skill_state_write_guard();

        // 获取 skill 信息
        let skill = db
            .get_installed_skill(id)?
            .ok_or_else(|| anyhow!("Skill not found: {id}"))?;

        // DB 行可能被同步导入污染(远端快照 raw SQL 直接灌库,绕过安装期校验),
        // 也可能是 v3.11.0 引入 sanitize_install_name 之前留下的存量脏值
        // (当年扫描不过滤点开头目录,`.github/SKILL.md` 会存成 `.github`)。
        //
        // 守卫失败时**跳过全部文件系统操作、但仍删除 DB 行**:`db.delete_skill`
        // 全项目只有这一处调用且未暴露为命令,若在此直接返回 Err,用户就再也无法
        // 从界面删掉这条记录,只能手改 SQLite。安全目标是「不碰危险路径」,
        // 不是「把用户锁在坏状态里」。
        let (backup_path, preserved_pi_path, pi_cleanup_incomplete) =
            match Self::require_valid_directory(&skill.directory) {
                Ok(directory) => {
                    let ssot_dir = Self::get_ssot_dir()?;
                    let source = ssot_dir.join(&directory);
                    let mcode_destination = if skill.apps.mcode {
                        let destination =
                            Self::get_distinct_app_skills_dir(&ssot_dir, &AppType::Mcode)?
                                .join(&directory);

View on GitHub (pinned to 06082e189d)