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
- Refresh the installed-skills list and retry only if the id is still present
- Treat 'Skill not found' during uninstall as already-gone when your goal is an idempotent delete, and simply refresh the UI
- 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
- Refresh the skill list after every uninstall before enabling further actions
- Pass ids taken from the freshly loaded list, never from long-lived caches
- Avoid two windows editing skills simultaneously
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
- Skill not found
- Skill no longer installed
- Cannot update local skill
- Pi 中已存在同名但内容不同的 Skill,拒绝覆盖或删除
- Pi 中的 Skill 已在操作期间发生变化,拒绝覆盖
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)