alibaba/nacos · error · RuntimeException

Failed to delete: {path}

Error message

Failed to delete: {path}

What it means

Thrown by the private deleteDirectory helper as a RuntimeException wrapping an IOException that occurred during Files.delete inside a Files.walk traversal. It is a fatal abort of the recursive directory deletion used by the OVERWRITE/BACKUP sync strategies when cleaning up a temp or existing skill directory.

Source

Thrown at api/src/main/java/com/alibaba/nacos/api/ai/model/skills/SkillUtils.java:522

    /**
     * Recursively delete a directory and all its contents.
     *
     * @param directory the directory to delete
     * @throws IOException if deletion fails
     */
    private static void deleteDirectory(Path directory) throws IOException {
        if (!Files.exists(directory)) {
            return;
        }
        
        // Delete files before directories
        Files.walk(directory)
            .sorted((a, b) -> b.compareTo(a))
            .forEach(path -> {
                try {
                    Files.delete(path);
                } catch (IOException e) {
                    throw new RuntimeException("Failed to delete: " + path, e);
                }
            });
    }
    
    /**
     * Main config dataId for skill.
     *
     * @deprecated No longer used. Replaced by {@link #SKILL_INDEX_DATA_ID} for the manifest
     *             and versioned resource files for content.
     */
    @Deprecated
    public static final String SKILL_MAIN_DATA_ID = "skill.json";
    
    /**
     * Resource config dataId prefix.
     */
    public static final String RESOURCE_DATA_ID_PREFIX = "resource_";
    

View on GitHub (pinned to 9b989acdf1)

Solutions

  1. On Windows, ensure no process (editor, indexer, AV) holds the directory open before syncing.
  2. Retry the sync after closing any handles to files under the skill directory.
  3. Grant the process delete/write permissions on the base directory.
  4. Use OVERWRITE only when the directory is quiescent; consider BACKUP to avoid deleting a locked tree.

Example fix

// before (caller)
SkillUtils.syncToLocal(skill, baseDir, ExistingDirectoryStrategy.OVERWRITE);
// -> RuntimeException: Failed to delete: ... (file locked)

// after (caller)
try {
    SkillUtils.syncToLocal(skill, baseDir, ExistingDirectoryStrategy.OVERWRITE);
} catch (RuntimeException e) {
    if (e.getCause() instanceof IOException) {
        log.warn("Skill dir locked, retrying with BACKUP");
        SkillUtils.syncToLocal(skill, baseDir, ExistingDirectoryStrategy.BACKUP);
    } else throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Ensure the directory is not locked before syncing (Windows)
Path skillDir = Paths.get(baseDir).resolve(skill.getName());
if (Files.exists(skillDir)) {
    try (var stream = Files.walk(skillDir)) {
        // touch each file to confirm no lock
        stream.forEach(p -> {});
    }
}

Try / catch

try {
    SkillUtils.syncToLocal(skill, baseDir, ExistingDirectoryStrategy.OVERWRITE);
} catch (RuntimeException e) {
    if (e.getCause() instanceof IOException io) {
        log.warn("Directory locked, falling back to BACKUP: {}", io.getMessage());
        SkillUtils.syncToLocal(skill, baseDir, ExistingDirectoryStrategy.BACKUP);
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: Any Files.delete(path) inside the walk throws IOException (file in use, permission denied, path vanished between walk and delete) and it is rethrown as RuntimeException("Failed to delete: " + path, e).

Common situations: On Windows a file is still open/locked by another process; a file is read-only and permissions deny deletion; an antivirus or indexer holds a handle; a race condition removed the file between the walk snapshot and the delete.

Related errors


AI-assisted analysis of alibaba/nacos@9b989acdf1 (2026-08-14). Data as JSON: /api/errors/7ac4967a4f54022b. Report an issue: GitHub.