{"record":{"id":"3bc7f51707c6f0b8","repo":"farion1231/cc-switch","slug":"skill-directory-conflict","errorCode":"SKILL_DIRECTORY_CONFLICT","errorMessage":"SKILL_DIRECTORY_CONFLICT","messagePattern":"SKILL_DIRECTORY_CONFLICT","errorType":"error_code","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src-tauri/src/services/skill.rs","lineNumber":736,"sourceCode":"                continue;\n            }\n\n            let same_repo = existing.repo_owner.as_deref() == Some(&skill.repo_owner)\n                && existing.repo_name.as_deref() == Some(&skill.repo_name);\n            if same_repo {\n                let mut updated = existing.clone();\n                updated.apps.set_enabled_for(current_app, true);\n                db.save_skill(&updated)?;\n                Self::sync_to_app_dir(&updated.directory, current_app)?;\n                log::info!(\n                    \"Skill {} 已存在，更新 {:?} 启用状态\",\n                    updated.name,\n                    current_app\n                );\n                return Ok(Some(updated));\n            }\n\n            return Err(anyhow!(format_skill_error(\n                \"SKILL_DIRECTORY_CONFLICT\",\n                &[\n                    (\"directory\", install_name),\n                    (\n                        \"existing_repo\",\n                        &format!(\n                            \"{}/{}\",\n                            existing.repo_owner.as_deref().unwrap_or(\"unknown\"),\n                            existing.repo_name.as_deref().unwrap_or(\"unknown\")\n                        )\n                    ),\n                    (\n                        \"new_repo\",\n                        &format!(\"{}/{}\", skill.repo_owner, skill.repo_name)\n                    ),\n                ],\n                Some(\"uninstallFirst\"),\n            )));","sourceCodeStart":718,"sourceCodeEnd":754,"githubUrl":"https://github.com/farion1231/cc-switch/blob/06082e189d65e6d6dbadc35dacdac1ce6c79d89a/src-tauri/src/services/skill.rs#L718-L754","documentation":"During skill install, the fast path reuse_existing_install finds an installed skill whose install_name (final directory segment) collides with the incoming one, but the existing record belongs to a different repository (owner/name mismatch), so it refuses to reuse or overwrite and returns a SKILL_DIRECTORY_CONFLICT error via format_skill_error. The payload is a JSON string with code, context (directory, existing_repo as 'owner/name'), and a suggestion key, which the frontend parses into a structured, actionable message. This protects the existing installation from being silently replaced by unrelated content that happens to share a directory name.","triggerScenarios":"Calling install() for a DiscoverableSkill from repo B whose directory field's last segment equals a skill already installed from repo A (e.g. both repos contain a top-level 'code-review' skill dir).","commonSituations":"Installing multiple skills collections that ship identically-named folders; a fork of a popular skills repo reinstalled under the fork's owner; renaming upstream changed the repo identity so the same logical skill now looks like a conflict.","solutions":["Uninstall the existing skill named in context.existing_repo first, then retry the install.","Install the incoming skill from its original repo instead of the fork/second source so owner/name match and the reuse path applies.","If both are wanted, change one skill's directory name at the source so install_name differs."],"exampleFix":"# before\ninstalled: { directory: \"code-review\", repo: \"alice/skills\" }\ninstall({ directory: \"code-review\", repo_owner: \"bob\", repo_name: \"skills\", ... })\n# -> SKILL_DIRECTORY_CONFLICT (directory=code-review, existing_repo=alice/skills)\n\n# after\nuninstall(\"code-review\")   # remove alice/skills version\ninstall({ directory: \"code-review\", repo_owner: \"bob\", repo_name: \"skills\", ... })  # succeeds","handlingStrategy":"try-catch","validationCode":"// Before install, query for a same-name skill from another repo\nlet existing = db.find_skill_by_directory(&install_name)?;\nif let Some(prev) = existing {\n    if prev.repo_owner != skill.repo_owner || prev.repo_name != skill.repo_name {\n        return plan_uninstall_first(&prev); // avoid the conflict deterministically\n    }\n}","typeGuard":null,"tryCatchPattern":"match install(&db, &skill, &app).await {\n    Err(e) if e.to_string().contains(\"SKILL_DIRECTORY_CONFLICT\") => {\n        let parsed: serde_json::Value = serde_json::from_str(&e.to_string())?;\n        let existing = parsed[\"context\"][\"existing_repo\"].as_str().unwrap_or(\"unknown\");\n        ui::confirm_uninstall(existing)?; // then retry install once\n    }\n    other => other?,\n}","preventionTips":["Show 'already installed from <repo>' state in the skill list before users click install.","When publishing skill repos, give folders unique names to avoid cross-repo install_name collisions."],"tags":["skills","install","conflict","duplicate","tauri"],"backgroundTag":"duplicate-identifier-conflict","analyzedSha":"06082e189d65e6d6dbadc35dacdac1ce6c79d89a","analyzedAt":"2026-08-20T14:29:00.113Z","contentChangedAt":"2026-08-20T14:29:00.113Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}