{"record":{"id":"5c4e614bf6661cb0","repo":"BigPizzaV3/CodexPlusPlus","slug":"skill","errorCode":null,"errorMessage":"skill 源目录不存在：{}","messagePattern":"skill 源目录不存在：(.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/codex-plus-core/src/skills.rs","lineNumber":442,"sourceCode":"            },\n        );\n        self.save_state_unlocked(&state)?;\n        drop(_guard);\n\n        self.set_enabled(&skill.id, true)?;\n        Ok(self.load_state())\n    }\n\n    /// 启用 = 在 `$CODEX_HOME/skills/` 下建一个指向源目录的软链；停用 = 删掉它。\n    pub fn set_enabled(&self, id: &str, enabled: bool) -> anyhow::Result<()> {\n        validate_skill_id(id)?;\n        let source = self.source_dir.join(id);\n        let link = self.linked_dir().join(id);\n        if !enabled {\n            return remove_link(&link);\n        }\n        if !source.is_dir() {\n            anyhow::bail!(\"skill 源目录不存在：{}\", source.display());\n        }\n        std::fs::create_dir_all(self.linked_dir())\n            .with_context(|| format!(\"创建 {} 失败\", self.linked_dir().display()))?;\n        remove_link(&link)?;\n        link_or_copy(&source, &link)\n    }\n\n    /// 卸载：删软链，源目录整体移进备份目录。不做自动轮转删除。\n    pub fn uninstall(&self, id: &str) -> anyhow::Result<SkillsState> {\n        validate_skill_id(id)?;\n        remove_link(&self.linked_dir().join(id))?;\n\n        let source = self.source_dir.join(id);\n        if source.is_dir() {\n            std::fs::create_dir_all(&self.backups_dir)\n                .with_context(|| format!(\"创建备份目录失败：{}\", self.backups_dir.display()))?;\n            let backup = self\n                .backups_dir","sourceCodeStart":424,"sourceCodeEnd":460,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6/crates/codex-plus-core/src/skills.rs#L424-L460","documentation":"set_enabled materializes a skill into the linked directory by creating a symlink/copy from the skill's source directory. If the expected source directory (source_dir joined with the skill id) does not exist or is not a directory, enabling is aborted with this message.","triggerScenarios":"Calling set_enabled(id, true) (directly or via install_from_zip / restore_backup) when self.source_dir.join(id) is missing or is a file, not a directory.","commonSituations":"Skill was deleted from the source dir while still registered; a wrong skills root directory is configured; a zip install was cleaned up or a backup restore references a removed path; case-mismatched skill id on a case-sensitive filesystem.","solutions":["Verify the skill directory exists at <source_dir>/<id> and re-add it if deleted","Check the configured skills source directory path in settings points to the right root","Reinstall the skill (install_from_zip or manual copy) before enabling it","Confirm the skill id casing matches the on-disk directory name exactly"],"exampleFix":"// before\nskills.set_enabled(\"my-skill\", true)?; // 源目录已删\n// after\nlet src = skills_root.join(\"my-skill\");\nif !src.is_dir() {\n    // 重新安装或修正 skills 根目录后再启用\n    anyhow::bail!(\"请先安装 skill my-skill 到 {}\", skills_root.display());\n}\nskills.set_enabled(\"my-skill\", true)?;","handlingStrategy":"validation","validationCode":"let src = skills_root.join(id);\nif !src.is_dir() {\n    anyhow::bail!(\"skill 源目录不存在：{}\", src.display());\n}","typeGuard":"fn skill_source_exists(skills_root: &Path, id: &str) -> bool {\n    skills_root.join(id).is_dir()\n}","tryCatchPattern":"match skills.set_enabled(id, true) {\n    Err(e) if e.to_string().starts_with(\"skill 源目录不存在\") => eprintln!(\"请先重新安装该 skill\"),\n    other => other?,\n}","preventionTips":["启用前确认 <source_dir>/<id> 存在","删除 skill 时同步清理注册状态","校验 skills 根目录配置","zip 安装后核对目录名与 id 大小写一致"],"tags":["filesystem","skills","missing-directory"],"backgroundTag":"directory-not-found","analyzedSha":"b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6","analyzedAt":"2026-09-19T23:35:21.129Z","contentChangedAt":"2026-09-19T23:35:21.129Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}