farion1231/cc-switch · error · anyhow::Error
Skill not found: {skill_id}
Error message
Skill not found: {skill_id} What it means
Entry lookup of SkillService::update_skill: the installed_skills table has no row with the given skill_id, so the update cannot even start. Unlike errors 42/43/49 this is not a mid-flight race — the id was already stale when the call was made.
Source
Thrown at src-tauri/src/services/skill.rs:1363
/// 使用只更新现有记录的 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)?;
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}")),
};View on GitHub (pinned to 0b5da51016)
Solutions
- Re-fetch the installed skills list and only offer Update for ids that still exist
- Skip (not fail) missing ids in batch update loops
- Refresh after any uninstall before enabling update actions
Example fix
// before
for (const s of cachedSkills) await invoke('update_skill', { id: s.id });
// after: re-read the source of truth, skip gone entries
const current = await invoke<Record<string, { id: string }>>('get_all_installed_skills');
for (const id of Object.keys(current)) {
await invoke('update_skill', { 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();
return;
}
await invoke('update_skill', { id }); Try / catch
try {
await invoke('update_skill', { id });
} catch (e) {
if (String(e).startsWith('Skill not found')) {
await refreshSkillList();
return;
}
throw e;
} Prevention
- Build update-all loops from a freshly fetched list, not a cached snapshot
- Skip missing ids in batch loops instead of aborting the batch
- Refresh after uninstall/profile-switch before offering updates
When it happens
Trigger: Clicking Update on a skill listed from a stale cache after it was uninstalled elsewhere; passing a wrong or outdated id to the update_skill command; 'update all' iterating a list captured before some rows were removed.
Common situations: UI list not refreshed after uninstall; duplicate windows on one database; batch update loops built from a snapshot.
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
- Skill not found: {id}
- Skill no longer installed: {}
- Cannot update local skill: {skill_id}
- Skill changed during update: {}
- Skill directory changed during install; please retry
AI-assisted analysis of farion1231/cc-switch@0b5da51016 (2026-08-20).
Data as JSON: /api/errors/773cb653cda5b07b.
Report an issue: GitHub.