{"record":{"id":"d7e2e44eea53775f","repo":"farion1231/cc-switch","slug":"invalid-skill-destination","errorCode":null,"errorMessage":"Invalid skill destination: {}","messagePattern":"Invalid skill destination: (.+?)","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src-tauri/src/services/skill.rs","lineNumber":2356,"sourceCode":"        }\n\n        let manifest = source.join(\"SKILL.md\");\n        if !manifest.is_file() {\n            return Err(anyhow!(\n                \"Skill 源目录缺少 SKILL.md，拒绝同步以避免覆盖目标目录: {}\",\n                source.display()\n            ));\n        }\n\n        Ok(())\n    }\n\n    fn replace_dest_with_copy(source: &Path, dest: &Path, directory: &str) -> Result<()> {\n        Self::validate_sync_source_dir(source, directory)?;\n\n        let parent = dest\n            .parent()\n            .ok_or_else(|| anyhow!(\"Invalid skill destination: {}\", dest.display()))?;\n        fs::create_dir_all(parent)?;\n\n        let nonce = SystemTime::now()\n            .duration_since(UNIX_EPOCH)\n            .unwrap_or_default()\n            .as_nanos();\n        let tmp_name = Self::sanitize_backup_segment(directory);\n        let tmp = parent.join(format!(\".{tmp_name}.tmp-{}-{nonce}\", std::process::id()));\n\n        if tmp.exists() || Self::is_symlink(&tmp) {\n            Self::remove_path(&tmp)?;\n        }\n\n        let copy_result = Self::copy_dir_recursive(source, &tmp);\n        if let Err(err) = copy_result {\n            let _ = Self::remove_path(&tmp);\n            return Err(err);\n        }","sourceCodeStart":2338,"sourceCodeEnd":2374,"githubUrl":"https://github.com/farion1231/cc-switch/blob/a2e22f330273a5b6ffa87cb8b82b624601bac562/src-tauri/src/services/skill.rs#L2338-L2374","documentation":"Thrown by replace_dest_with_copy when dest.parent() returns None, i.e. the computed destination path is a filesystem root with no parent component. This is a defensive assertion: dest is always app_dir.join(directory), so reaching it requires the app skills directory to have degenerated to a root-level path.","triggerScenarios":"A custom/misconfigured app skills directory that resolves to \"/\" or an empty path, making dest a root path. Not reachable through normal configuration where app_dir is a normal absolute directory.","commonSituations":"Manually edited settings pointing the app skills dir at a filesystem root; a path-resolution regression in get_distinct_app_skills_dir. Practically never seen in the wild.","solutions":["Print/log dest.display() from the error and check what the app skills directory resolved to","Fix the app skills directory setting back to a normal absolute directory (e.g. ~/.claude/skills)","If it reproduces with valid settings, file a bug against get_distinct_app_skills_dir path handling"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Rust caller — sanity-check the app skills dir setting before sync\nlet app_dir = SkillService::get_distinct_app_skills_dir(&ssot, &app)?;\nif app_dir.parent().is_none() || app_dir == Path::new(\"/\") {\n    return Err(anyhow!(\"app skills dir misconfigured: {}\", app_dir.display()));\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep app skills directories configured as normal absolute paths, never a filesystem root","Validate custom directory settings at write time, not only at use time"],"tags":["filesystem","sync","defensive","path"],"backgroundTag":null,"analyzedSha":"a2e22f330273a5b6ffa87cb8b82b624601bac562","analyzedAt":"2026-08-16T03:46:07.889Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}