{"record":{"id":"4dcc061e77c67a99","repo":"siyuan-note/siyuan","slug":"user-skill-is-read-only-s","errorCode":null,"errorMessage":"user skill is read-only: %s","messagePattern":"user skill is read-only: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/util/skill.go","lineNumber":444,"sourceCode":"\tif err := os.MkdirAll(dir, 0755); err != nil {\n\t\treturn err\n\t}\n\tskillDir := filepath.Join(dir, name)\n\tif err := os.MkdirAll(skillDir, 0755); err != nil {\n\t\treturn err\n\t}\n\tskillMdPath := filepath.Join(skillDir, \"SKILL.md\")\n\treturn filelock.WriteFile(skillMdPath, []byte(content))\n}\n\nfunc RemoveSkill(name string) error {\n\tif err := validateSkillName(name); err != nil {\n\t\treturn err\n\t}\n\trecord, ok := findSkillRecord(readSkillRecords(SkillsDir()), name)\n\tif !ok {\n\t\tif _, found := findSkillRecord(readSkillRecords(UserSkillsDir()), name); found {\n\t\t\treturn fmt.Errorf(\"user skill is read-only: %s\", name)\n\t\t}\n\t\treturn fmt.Errorf(\"skill not found: %s\", name)\n\t}\n\tskillDir := filepath.Join(record.Root, record.DirName)\n\treturn os.RemoveAll(skillDir)\n}\n\nfunc RenameSkill(oldName, newName string) error {\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 {","sourceCodeStart":426,"sourceCodeEnd":462,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/util/skill.go#L426-L462","documentation":"RemoveSkill only deletes skills that live in the workspace SkillsDir(). If the name matches a skill found under UserSkillsDir() (bundled/installed skills shipped with the application), the library refuses to delete it and reports 'user skill is read-only'. User skills are treated as managed content, not user-editable data.","triggerScenarios":"RemoveSkill(name) / removeSkill / skillRemove called on a skill whose record resolves from UserSkillsDir() rather than SkillsDir() — i.e. attempting to delete a built-in/installed skill that exists only in the user-skills directory.","commonSituations":"Trying to clean up 'unused' skills that are actually shipped defaults; scripts iterating all visible skill names and deleting them; confusion between workspace skills and installed user skills after upgrading the app.","solutions":["Do not remove user skills — they are read-only by design; leave them enabled/disabled instead","If you want an equivalent workspace copy you can manage, create a new skill in the workspace with the desired content (SaveSkill) rather than deleting the user skill","Filter user-skill names out of any bulk-delete routine before calling RemoveSkill","Disable the user skill in settings if it should not be visible, instead of deleting it"],"exampleFix":"// before\nfor _, name := range allVisibleSkillNames {\n    RemoveSkill(name) // errors on user skills\n}\n// after\nfor _, name := range workspaceOnlySkillNames {\n    RemoveSkill(name) // only skills under SkillsDir()\n}","handlingStrategy":"try-catch","validationCode":"// Go: distinguish roots before deleting\nif _, ok := findSkillRecord(readSkillRecords(UserSkillsDir()), name); ok {\n    return fmt.Errorf(\"%s is a read-only user skill; skipping\", name)\n}","typeGuard":null,"tryCatchPattern":"if err := RemoveSkill(name); err != nil {\n    if strings.Contains(err.Error(), \"user skill is read-only\") {\n        log.Printf(\"skipped built-in skill %s\", name)\n    } else {\n        return err\n    }\n}","preventionTips":["Treat UserSkillsDir() content as immutable; only manage SkillsDir() content","Filter user-skill names out of bulk-delete jobs","Use enable/disable settings instead of deletion for built-in skills"],"tags":["go","skills","read-only","permissions"],"backgroundTag":"permission-denied","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}