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
- Catch the error, refresh the skill list, and inform the user the skill was uninstalled during the update
- Re-install the skill from its repo if the update is still wanted
- 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
- Disable uninstall/toggle on a skill row while its update request is in flight
- Do not run 'update all' while the user can click uninstall on the same rows
- Treat this error as terminal for the attempt, never auto-retry it
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
- Skill changed during update
- Cannot update local skill
- Pi 中的 Skill 已在操作期间发生变化,拒绝覆盖
- Skill directory changed during install; please retry
- Skill not found
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)