farion1231/cc-switch · error · anyhow::Error
Skill directory changed during install; please retry
Error message
Skill directory changed during install; please retry
What it means
Thrown during skill installation when the SSOT destination directory did not exist at the final copy step but no downloaded source is available. The install flow skips the network download when dest already exists (src-tauri/src/services/skill.rs:812); downloaded_source is only populated in that download branch. A concurrent install/uninstall removed dest between the fast-path check and re-acquiring the write guard, so the code has neither an existing directory nor a downloaded copy to install from.
Source
Thrown at src-tauri/src/services/skill.rs:908
&skill.directory,
);
let readme_url =
Self::build_skill_doc_url(&skill.repo_owner, &skill.repo_name, &repo_branch, &doc_path);
// Re-check after the network download: another install/uninstall may have
// completed while the lock was intentionally released around `.await`.
let _state_guard = skill_state_write_guard();
if let Some(existing) = Self::reuse_existing_install(db, skill, &install_name, current_app)?
{
return Ok(existing);
}
if !dest.exists() {
let source = downloaded_source
.as_ref()
.map(|(_, source)| source)
.ok_or_else(|| anyhow!("Skill directory changed during install; please retry"))?;
Self::preflight_install_destination(source, &install_name, current_app)?;
Self::copy_dir_recursive(source, &dest)?;
}
// 创建 InstalledSkill 记录
// 计算内容哈希
let content_hash = Self::compute_dir_hash(&dest).map(Some).unwrap_or_else(|e| {
log::warn!("Failed to compute content hash for {}: {e}", install_name);
None
});
let installed_skill = InstalledSkill {
id: skill.key.clone(),
name: skill.name.clone(),
description: if skill.description.is_empty() {
None
} else {
Some(skill.description.clone())View on GitHub (pinned to a2e22f3302)
Solutions
- Retry the install command — the second attempt takes the download branch and succeeds
- Avoid firing install and uninstall for the same skill concurrently from the frontend (disable the button while a command is in flight)
- If it persists, inspect the SSOT dir for a half-deleted directory and remove it, then retry
Example fix
// before
const skill = await invoke('install_skill', { repo });
// after
async function installWithRetry(repo: SkillRepo) {
try { return await invoke('install_skill', { repo }); }
catch (e) {
if (String(e).includes('changed during install'))
return invoke('install_skill', { repo }); // second pass downloads
throw e;
}
} Defensive patterns
Strategy: retry
Validate before calling
const skills = await invoke('list_installed_skills');
const busy = skills.find(s => s.directory === skill.directory);
// ensure no uninstall/install for this directory is currently in flight
if (inFlight.has(skill.directory)) return; // serialize per-directory Try / catch
try { await invoke('install_skill', { repo }); }
catch (e) {
if (String(e).includes('changed during install'))
return invoke('install_skill', { repo }); // second pass downloads
throw e;
} Prevention
- Serialize install/uninstall per skill directory in the frontend (disable buttons while a command is in flight)
- Retry once on this specific message — the retry path always downloads
When it happens
Trigger: Calling install_skill for a skill whose SSOT directory already exists (no download occurs), while a concurrent uninstall of the same skill (or a cloud-sync snapshot import that wipes SSOT) deletes that directory during the window where the state write guard is released around .await. On re-check reuse_existing_install returns None and dest.exists() is false with downloaded_source == None.
Common situations: Double-clicking install in the UI firing two commands, uninstall and install racing from different windows, or a background cloud-sync import mutating SSOT while the user reinstalls. Typically self-healing on the next attempt because the retry path sees dest missing and downloads.
Related errors
- Skill no longer installed: {}
- Skill 存储目录不能与 {app:?} 的 Skills 目录相同: {}
- Skill not found: {id}
- Unsupported URL scheme
- Invalid URL
AI-assisted analysis of farion1231/cc-switch@a2e22f3302 (2026-08-16).
Data as JSON: /api/errors/a7d6a81299005f93.
Report an issue: GitHub.