{"record":{"id":"1ee5f27e540dc596","repo":"farion1231/cc-switch","slug":"skill-changed-during-update","errorCode":null,"errorMessage":"Skill changed during update: {}","messagePattern":"Skill changed during update: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src-tauri/src/services/skill.rs","lineNumber":1454,"sourceCode":"            })?;\n\n        // Downloads do not mutate local state, so acquire only now and hold the\n        // guard through the SSOT replacement, DB metadata update, and app sync.\n        let _state_guard = skill_state_write_guard();\n\n        // 下载和扫描期间用户可能已经卸载了该 Skill。必须在任何备份、删除或\n        // 复制之前重新确认记录仍存在；否则即使最终的 metadata UPDATE 能发现\n        // 缺行，这里也会先把已卸载的 SSOT 目录重新创建出来。\n        let mut current_skill = db\n            .get_installed_skill(&skill.id)?\n            .ok_or_else(|| anyhow!(\"Skill no longer installed: {}\", skill.id))?;\n        if current_skill.directory != skill.directory\n            || current_skill.repo_owner != skill.repo_owner\n            || current_skill.repo_name != skill.repo_name\n            || current_skill.repo_branch != skill.repo_branch\n            || current_skill.installed_at != skill.installed_at\n        {\n            return Err(anyhow!(\"Skill changed during update: {}\", skill.id));\n        }\n        Self::require_valid_directory(&current_skill.directory)?;\n        current_skill.apps.pi = Self::skill_exists_in_app(&current_skill.directory, &AppType::Pi);\n        let skill = current_skill;\n\n        let dest = ssot_dir.join(&skill.directory);\n        let pi_deployment = if skill.apps.pi {\n            let pi_dir = Self::get_distinct_app_skills_dir(&ssot_dir, &AppType::Pi)?;\n            let pi_destination = pi_dir.join(&skill.directory);\n            Self::inspect_pi_skill_destination(&dest, &pi_destination, &skill.directory)?\n        } else {\n            None\n        };\n\n        // 备份旧文件\n        let _ = Self::create_uninstall_backup(&skill);\n\n        // 删除旧 SSOT 目录并复制新文件","sourceCodeStart":1436,"sourceCodeEnd":1472,"githubUrl":"https://github.com/farion1231/cc-switch/blob/0b5da510168914b251481654a568c3ffacd62cf4/src-tauri/src/services/skill.rs#L1436-L1472","documentation":"The row still exists after the download, but directory, repo_owner, repo_name, repo_branch, or installed_at differs from the snapshot taken before the download. Another update/reinstall replaced the record during the await window, and acting on the mixed identity (old snapshot paths + new row) would be unsafe, so the operation aborts.","triggerScenarios":"Two concurrent update_skill calls for the same id; uninstall followed by reinstall racing an in-flight update; profile sync overwriting the row with a different generation (installed_at is the generation marker).","commonSituations":"Double-clicking Update; 'update all' racing a manual update of the same skill; switching profiles while an update runs.","solutions":["Retry the update once from a freshly loaded skill record — the new snapshot will match the new generation","Serialize update/uninstall/reinstall per skill id in the UI","Avoid switching profiles or triggering syncs while batch updates are running"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await invoke('update_skill', { id });\n} catch (e) {\n  if (String(e).includes('Skill changed during update')) {\n    await refreshSkillList(); // load the new generation\n    return invoke('update_skill', { id }); // retry once from fresh snapshot\n  }\n  throw e;\n}","preventionTips":["Serialize update/reinstall per skill id — one operation at a time","Debounce double-clicked Update buttons","Do not switch profiles or trigger syncs while updates are running"],"tags":["update","race-condition","consistency","skill-manager"],"backgroundTag":"optimistic-concurrency-conflict","analyzedSha":"0b5da510168914b251481654a568c3ffacd62cf4","analyzedAt":"2026-08-20T14:29:00.113Z","contentChangedAt":"2026-08-20T14:29:00.113Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}