siyuan-note/siyuan · error
skill already exists
Error message
skill already exists: %s
What it means
RenameSkill checks os.Stat(newDir) before renaming and returns 'skill already exists: %s' if a directory with the new name already exists in SkillsDir(). This prevents overwriting an existing skill via rename.
Solutions
- Pick a unique newName that does not collide with existing skills
- Remove or rename the conflicting skill first if the collision is intentional
- Generate a suffix-based unique name when automating renames
- Check existence of the target name before calling RenameSkill
Example fix
// before
RenameSkill("draft", "review") // "review" already exists
// after
newName := "review"
if skillExists(newName) {
newName = "review-2"
}
err := RenameSkill("draft", newName) Defensive patterns
Strategy: validation
Validate before calling
if (skillExists(newName)) {
newName = uniquifyName(newName); // e.g. append -2, -3
} Try / catch
try {
await renameSkill(oldName, newName);
} catch (e) {
if (String(e.message).startsWith("skill already exists")) {
newName = uniquifyName(newName);
await renameSkill(oldName, newName);
}
} Prevention
- Check target-name collisions before renaming
- Generate unique names programmatically for automated renames
- Note that case-insensitive filesystems treat 'Foo' and 'foo' as the same directory
When it happens
Trigger: Calling RenameSkill(oldName, newName) when a skill directory named newName already exists in SkillsDir().
Common situations: Renaming to a name that collides with another installed skill; retrying a partially completed rename; case-only rename on a case-insensitive filesystem colliding with the original.
Understand the failure class
Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.
Related errors
- created doc path [ ] already exists
- marketplace package install path already exists
- skill directory already exists or is invalid
- skill directory contains a link or special file
- skill path is not a regular file or directory
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/02b5cdcba139010e.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/util/skill.go:476
defer skillManagementLock.Unlock()
if err := validateSkillName(oldName); err != nil {
return err
}
if err := validateSkillName(newName); err != nil {
return err
}
dir := SkillsDir()
record, ok := findSkillRecord(readSkillRecords(dir), oldName)
if !ok {
if _, found := findSkillRecord(readSkillRecords(UserSkillsDir()), oldName); found {
return fmt.Errorf("user skill is read-only: %s", oldName)
}
return fmt.Errorf("skill not found: %s", oldName)
}
oldDir := filepath.Join(dir, record.DirName)
newDir := filepath.Join(dir, newName)
if _, err := os.Stat(newDir); err == nil {
return fmt.Errorf("skill already exists: %s", newName)
}
return os.Rename(oldDir, newDir)
}
func parseSkillFrontmatter(text string) (fm map[string]string, body string) {
fm = map[string]string{}
text = strings.TrimSpace(text)
if !strings.HasPrefix(text, "---") {
return fm, text
}
end := strings.Index(text[3:], "\n---")
if end < 0 {
return fm, text
}
raw := text[3 : 3+end]
body = strings.TrimSpace(text[3+end+4:])
for line := range strings.SplitSeq(raw, "\n") {
line = strings.TrimSpace(line)View on GitHub (pinned to 9f775e8a12)