siyuan-note/siyuan · warning
skill already exists: %s
Error message
skill already exists: %s
What it means
Thrown by RenameSkill when the destination (newName) directory already exists. The function stats newDir; if it exists the rename is refused to prevent silently overwriting an installed skill. Unlike install (which removes the old dir first), rename treats the target as occupied.
Source
Thrown at kernel/util/skill.go:174
}
return os.RemoveAll(skillDir)
}
func RenameSkill(oldName, newName string) error {
if err := validateSkillName(oldName); err != nil {
return err
}
if err := validateSkillName(newName); err != nil {
return err
}
dir := SkillsDir()
oldDir := filepath.Join(dir, oldName)
newDir := filepath.Join(dir, newName)
if _, err := os.Stat(oldDir); os.IsNotExist(err) {
return fmt.Errorf("skill not found: %s", oldName)
}
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 251596fc0d)
Solutions
- Check DiscoverSkills for the target name before renaming; prompt the user to overwrite or pick another name.
- If overwrite is intended, RemoveSkill(newName) before RenameSkill, mirroring the install path.
- Normalize name case consistently and reject collisions at the UI layer.
Example fix
// before
err := util.RenameSkill(oldName, newName)
// after — explicit overwrite when the user confirms
if _, serr := os.Stat(filepath.Join(util.SkillsDir(), newName)); serr == nil {
_ = util.RemoveSkill(newName)
}
err := util.RenameSkill(oldName, newName) Defensive patterns
Strategy: validation
Validate before calling
// reject or confirm overwrite before renaming
exists := false
for _, s := range util.DiscoverSkills() {
if strings.EqualFold(s.Name, newName) {
exists = true
break
}
}
if exists && !userConfirmedOverwrite {
return fmt.Errorf("target name %q already in use", newName)
} Prevention
- Normalize skill names to a single case convention to avoid case-collision surprises.
- Offer an explicit 'replace existing' choice when the target name is taken.
- On case-insensitive OSes, treat name equality case-insensitively in collision checks.
When it happens
Trigger: Renaming a skill to a name already present in SkillsDir(); renaming to a case variant of an existing skill on a case-insensitive filesystem; attempting to 'restore' a skill whose name is taken.
Common situations: User picks a name already in use; a skill was partially installed and left a directory behind; case-insensitive OS (macOS/Windows) collides 'MySkill' with 'myskill'.
Related errors
- template already exists, use --overwrite to replace: %s
- The notebook already exists
- install skill %s failed: %s
- export source [%s] is not a regular file
- refuse to write decrypted asset inside workspace
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/02b5cdcba139010e.
Report an issue: GitHub.