{"record":{"id":"b538bf538edd23a3","repo":"farion1231/cc-switch","slug":"pi-skill-directory-b538bf","errorCode":null,"errorMessage":"Pi 中的 Skill 已在操作期间发生变化，拒绝覆盖: {directory}","messagePattern":"Pi 中的 Skill 已在操作期间发生变化，拒绝覆盖: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src-tauri/src/services/skill.rs","lineNumber":2190,"sourceCode":"    ) -> Result<()> {\n        match Self::inspect_pi_skill_destination(source, destination, directory)? {\n            Some(_) => Self::remove_path(destination),\n            None => Ok(()),\n        }\n    }\n\n    fn refresh_pi_skill_destination(\n        source: &Path,\n        destination: &Path,\n        directory: &str,\n        deployment: &PiSkillDeployment,\n    ) -> Result<()> {\n        Self::validate_sync_source_dir(source, directory)?;\n\n        match deployment {\n            PiSkillDeployment::Symlink { expected_target } => {\n                if !Self::is_symlink(destination) {\n                    return Err(anyhow!(\n                        \"Pi 中的 Skill 已在操作期间发生变化，拒绝覆盖: {directory}\"\n                    ));\n                }\n                let target = fs::read_link(destination)?;\n                let resolved = if target.is_absolute() {\n                    target\n                } else {\n                    destination\n                        .parent()\n                        .map(|parent| parent.join(&target))\n                        .unwrap_or(target)\n                };\n                if &resolved != expected_target {\n                    return Err(anyhow!(\n                        \"Pi 中的 Skill 已在操作期间发生变化，拒绝覆盖: {directory}\"\n                    ));\n                }\n                Self::remove_path(destination)?;","sourceCodeStart":2172,"sourceCodeEnd":2208,"githubUrl":"https://github.com/farion1231/cc-switch/blob/0b5da510168914b251481654a568c3ffacd62cf4/src-tauri/src/services/skill.rs#L2172-L2208","documentation":"refresh_pi_skill_destination recorded the deployment as Symlink during inspection, but at refresh time is_symlink(destination) is false — something replaced the symlink with a regular file or directory between the two checks (time-of-check/time-of-use). The service aborts instead of blind-removing the new occupant.","triggerScenarios":"The Pi app or a user rewrote the destination path while an install/update/sync was between its inspect and refresh phases; two concurrent sync operations retargeting the same Pi path.","commonSituations":"Long-running syncs on slow disks; Pi running simultaneously and rewriting its skills dir; concurrent app instances.","solutions":["Retry the operation — it re-inspects current state and will classify the new occupant correctly","If it persists, inspect the Pi-side path manually and resolve the conflict (see error 56 guidance)","Run only one app instance and avoid editing Pi's skills directory during syncs"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await invoke('update_skill', { id });\n} catch (e) {\n  if (String(e).includes('在操作期间发生变化')) {\n    await sleep(500);\n    return invoke('update_skill', { id }); // re-inspect resolves the new state\n  }\n  throw e;\n}","preventionTips":["Close the Pi app or pause its skill rewriting during syncs","Run one app instance so only one sync task touches Pi's directory","If retry keeps failing, resolve the Pi-side conflict manually (error 56 guidance)"],"tags":["pi","symlink","toctou","race-condition","skill-manager"],"backgroundTag":"concurrent-file-modification","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"}