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

Skill no longer installed

Error message

Skill no longer installed: {}

What it means

persist_updated_skill_metadata deliberately uses an UPDATE-only DAO (update_skill_metadata) that returns false when zero rows matched, instead of an upsert. False means the installed_skills row was deleted — typically uninstalled — while update_skill was awaiting the repo download, so the fresh metadata has nowhere to land.

Solutions

  1. Catch the error, refresh the skill list, and inform the user the skill was uninstalled during the update
  2. Re-install the skill from its repo if the update is still wanted
  3. Disable uninstall/toggle actions for a skill while its update request is in flight

Example fix

// before
const updated = await invoke('update_skill', { id });

// after: handle the mid-update uninstall
try {
  const updated = await invoke('update_skill', { id });
} catch (e) {
  if (String(e).includes('Skill no longer installed')) {
    await refreshSkillList(); // row is gone; do not retry the update
    notify('Skill was uninstalled during update');
  } else throw e;
}
Defensive patterns

Strategy: try-catch

Try / catch

try {
  await invoke('update_skill', { id });
} catch (e) {
  if (String(e).includes('Skill no longer installed')) {
    await refreshSkillList(); // terminal: row deleted mid-update, do not retry
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: User clicks uninstall while an update_skill call is still downloading the repo; a profile sync or second window deletes the row during the update's await window; two concurrent update_skill calls where one path removed the record.

Common situations: Update and uninstall buttons both enabled during a slow update; batch 'update all' racing individual uninstalls; slow network making the download window long enough for the user to act.

Related errors


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

Appendix: source

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

                    });
                }
            }
        }

        Ok(updates)
    }

    /// 持久化更新后的 Skill 元数据,并重新读取数据库中的权威应用启用状态。
    ///
    /// 更新过程包含网络下载,期间用户可能切换启用状态或卸载 Skill。这里必须
    /// 使用只更新现有记录的 DAO,避免旧快照覆盖 `enabled_*`,也避免已卸载记录
    /// 被重新插入。
    fn persist_updated_skill_metadata(
        db: &Arc<Database>,
        updated_skill: &InstalledSkill,
    ) -> Result<InstalledSkill> {
        if !db.update_skill_metadata(updated_skill)? {
            return Err(anyhow!("Skill no longer installed: {}", updated_skill.id));
        }

        db.get_installed_skill(&updated_skill.id)?
            .ok_or_else(|| anyhow!("Skill no longer installed: {}", updated_skill.id))
    }

    /// 更新单个 Skill(重新下载并替换本地文件)
    pub async fn update_skill(&self, db: &Arc<Database>, skill_id: &str) -> Result<InstalledSkill> {
        let mut skill = db
            .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)?;

View on GitHub (pinned to 06082e189d)