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
- On Windows, ensure no process (editor, indexer, AV) holds the directory open before syncing.
- Retry the sync after closing any handles to files under the skill directory.
- Grant the process delete/write permissions on the base directory.
- 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
- On Windows, close all handles (editors, indexers) under the skill dir before syncing.
- Prefer BACKUP on platforms where files are frequently locked.
- Run syncs when the directory is quiescent (no active readers).
- Grant the process full write/delete permissions on the base directory.
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
- Path escapes target directory: {target} is outside {baseDir}
- Skill directory already exists: {skillDir}
- Absolute path not allowed: {path}
- Invalid ZIP data: too short ({length} bytes)
- Invalid ZIP data: missing ZIP magic header (PK\x03\x04)
AI-assisted analysis of alibaba/nacos@9b989acdf1 (2026-08-14).
Data as JSON: /api/errors/7ac4967a4f54022b.
Report an issue: GitHub.