{"record":{"id":"a6f914cb56772ab3","repo":"alibaba/nacos","slug":"skill-directory-already-exists-skilldir","errorCode":null,"errorMessage":"Skill directory already exists: {skillDir}","messagePattern":"Skill directory already exists: (.+?)","errorType":"validation","errorClass":"FileAlreadyExistsException","httpStatus":null,"severity":"warning","filePath":"api/src/main/java/com/alibaba/nacos/api/ai/model/skills/SkillUtils.java","lineNumber":389,"sourceCode":"    }\n    \n    /**\n     * Core implementation for syncing Skill to local directory.\n     * This method contains the common logic for all syncToLocal variants.\n     *\n     * @param skill the Skill object to sync\n     * @param skillDir the target skill directory path\n     * @param basePath the base directory path\n     * @param strategy the strategy for handling existing directories\n     * @throws IOException if file operations fail\n     * @throws FileAlreadyExistsException if directory exists and strategy is FAIL\n     */\n    private static void syncToLocalCore(Skill skill, Path skillDir, Path basePath,\n        ExistingDirectoryStrategy strategy) throws IOException {\n        // Step 1: If strategy is FAIL, check if directory exists and throw exception immediately\n        if (strategy == ExistingDirectoryStrategy.FAIL) {\n            if (Files.exists(skillDir) && Files.isDirectory(skillDir)) {\n                throw new FileAlreadyExistsException(\"Skill directory already exists: \" + skillDir);\n            }\n        }\n        \n        // Step 2: Create temporary directory and write all files\n        String dirName = skillDir.getFileName().toString();\n        Path tempSkillDir = basePath.resolve(dirName + \".tmp.\" + System.currentTimeMillis());\n        \n        try {\n            // Create temporary skill directory\n            Files.createDirectories(tempSkillDir);\n            \n            // Write SKILL.md file\n            String markdownContent = toMarkdown(skill);\n            Path skillMdPath = tempSkillDir.resolve(\"SKILL.md\");\n            Files.write(skillMdPath, markdownContent.getBytes(StandardCharsets.UTF_8));\n            \n            // Write resource files\n            if (skill.getResource() != null && !skill.getResource().isEmpty()) {","sourceCodeStart":371,"sourceCodeEnd":407,"githubUrl":"https://github.com/alibaba/nacos/blob/9b989acdf181d00898f2e8839257bb2b2a3cefe3/api/src/main/java/com/alibaba/nacos/api/ai/model/skills/SkillUtils.java#L371-L407","documentation":"Thrown by syncToLocalCore as a java.nio.file.FileAlreadyExistsException when the ExistingDirectoryStrategy is FAIL and the target skill directory already exists on disk. This is an intentional 'do not clobber' guard: the caller explicitly chose FAIL and the directory was present.","triggerScenarios":"Calling syncToLocal with ExistingDirectoryStrategy.FAIL when {baseDir}/{skillName} (or {baseDir}/{skillDirName}) already exists as a directory.","commonSituations":"A previous sync created the directory; the same skill was synced twice; a directory was created out-of-band. The caller wanted to detect existing data rather than overwrite it.","solutions":["Switch the strategy to OVERWRITE (delete and recreate) or BACKUP (rename old with timestamp) if clobbering is acceptable.","If FAIL was intentional, catch FileAlreadyExistsException and treat it as 'already synced — skip'.","Check Files.exists(skillDir) first and branch on whether to sync at all."],"exampleFix":"// before\nSkillUtils.syncToLocal(skill, baseDir, ExistingDirectoryStrategy.FAIL); // throws if exists\n\n// after\n// Option A: overwrite\nSkillUtils.syncToLocal(skill, baseDir, ExistingDirectoryStrategy.OVERWRITE);\n// Option B: keep existing\ntry {\n    SkillUtils.syncToLocal(skill, baseDir, ExistingDirectoryStrategy.FAIL);\n} catch (FileAlreadyExistsException e) {\n    // already present, nothing to do\n}","handlingStrategy":"validation","validationCode":"Path skillDir = Paths.get(baseDir).resolve(dirName);\nif (Files.exists(skillDir) && Files.isDirectory(skillDir)) {\n    // already synced — skip, or choose OVERWRITE/BACKUP\n    return;\n}\nSkillUtils.syncToLocal(skill, baseDir, skillDirName, ExistingDirectoryStrategy.FAIL);","typeGuard":"static boolean skillDirMissing(String baseDir, String dirName) {\n    return !Files.isDirectory(Paths.get(baseDir).resolve(dirName));\n}","tryCatchPattern":"try {\n    SkillUtils.syncToLocal(skill, baseDir, ExistingDirectoryStrategy.FAIL);\n} catch (FileAlreadyExistsException e) {\n    // idempotent: already present\n}","preventionTips":["Use FAIL only when you need to detect pre-existing data; prefer OVERWRITE for idempotent re-syncs.","Make sync operations idempotent by catching FileAlreadyExistsException as 'already done'.","Check existence up front and branch rather than relying on the exception for control flow."],"tags":["java","nacos","ai","skills","filesystem","validation"],"backgroundTag":null,"analyzedSha":"9b989acdf181d00898f2e8839257bb2b2a3cefe3","analyzedAt":"2026-08-14T07:17:31.569Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}