{"record":{"id":"02b5cdcba139010e","repo":"siyuan-note/siyuan","slug":"skill-already-exists-s","errorCode":null,"errorMessage":"skill already exists: %s","messagePattern":"skill already exists: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/util/skill.go","lineNumber":476,"sourceCode":"\tdefer skillManagementLock.Unlock()\n\tif err := validateSkillName(oldName); err != nil {\n\t\treturn err\n\t}\n\tif err := validateSkillName(newName); err != nil {\n\t\treturn err\n\t}\n\tdir := SkillsDir()\n\trecord, ok := findSkillRecord(readSkillRecords(dir), oldName)\n\tif !ok {\n\t\tif _, found := findSkillRecord(readSkillRecords(UserSkillsDir()), oldName); found {\n\t\t\treturn fmt.Errorf(\"user skill is read-only: %s\", oldName)\n\t\t}\n\t\treturn fmt.Errorf(\"skill not found: %s\", oldName)\n\t}\n\toldDir := filepath.Join(dir, record.DirName)\n\tnewDir := filepath.Join(dir, newName)\n\tif _, err := os.Stat(newDir); err == nil {\n\t\treturn fmt.Errorf(\"skill already exists: %s\", newName)\n\t}\n\treturn os.Rename(oldDir, newDir)\n}\n\nfunc parseSkillFrontmatter(text string) (fm map[string]string, body string) {\n\tfm = map[string]string{}\n\ttext = strings.TrimSpace(text)\n\tif !strings.HasPrefix(text, \"---\") {\n\t\treturn fm, text\n\t}\n\tend := strings.Index(text[3:], \"\\n---\")\n\tif end < 0 {\n\t\treturn fm, text\n\t}\n\traw := text[3 : 3+end]\n\tbody = strings.TrimSpace(text[3+end+4:])\n\tfor line := range strings.SplitSeq(raw, \"\\n\") {\n\t\tline = strings.TrimSpace(line)","sourceCodeStart":458,"sourceCodeEnd":494,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/util/skill.go#L458-L494","documentation":"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.","triggerScenarios":"Calling RenameSkill(oldName, newName) when a skill directory named newName already exists in SkillsDir().","commonSituations":"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.","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"],"exampleFix":"// before\nRenameSkill(\"draft\", \"review\") // \"review\" already exists\n// after\nnewName := \"review\"\nif skillExists(newName) {\n    newName = \"review-2\"\n}\nerr := RenameSkill(\"draft\", newName)","handlingStrategy":"validation","validationCode":"if (skillExists(newName)) {\n  newName = uniquifyName(newName); // e.g. append -2, -3\n}","typeGuard":null,"tryCatchPattern":"try {\n  await renameSkill(oldName, newName);\n} catch (e) {\n  if (String(e.message).startsWith(\"skill already exists\")) {\n    newName = uniquifyName(newName);\n    await renameSkill(oldName, newName);\n  }\n}","preventionTips":["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"],"tags":["conflict","filesystem","skills"],"backgroundTag":"file-already-exists","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}