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

  1. Check DiscoverSkills for the target name before renaming; prompt the user to overwrite or pick another name.
  2. If overwrite is intended, RemoveSkill(newName) before RenameSkill, mirroring the install path.
  3. 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

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


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/02b5cdcba139010e. Report an issue: GitHub.