{"record":{"id":"027d4d50d0d4d713","repo":"siyuan-note/siyuan","slug":"skill-not-found-s","errorCode":null,"errorMessage":"skill not found: %s","messagePattern":"skill not found: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"kernel/util/skill.go","lineNumber":128,"sourceCode":"\tif strings.ContainsAny(name, `/\\`) {\n\t\treturn fmt.Errorf(\"invalid skill name: %s\", name)\n\t}\n\tdir := SkillsDir()\n\tabs := filepath.Join(dir, name)\n\tif !gulu.File.IsSubPath(dir, abs) {\n\t\treturn fmt.Errorf(\"invalid skill name: %s\", name)\n\t}\n\treturn nil\n}\n\nfunc ReadSkill(name string) (string, error) {\n\tif err := validateSkillName(name); err != nil {\n\t\treturn \"\", err\n\t}\n\tskillMdPath := filepath.Join(SkillsDir(), name, \"SKILL.md\")\n\tb, err := filelock.ReadFile(skillMdPath)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"skill not found: %s\", name)\n\t}\n\treturn string(b), nil\n}\n\nfunc SaveSkill(name, content string) error {\n\tif err := validateSkillName(name); err != nil {\n\t\treturn err\n\t}\n\tdir := SkillsDir()\n\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))","sourceCodeStart":110,"sourceCodeEnd":146,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/util/skill.go#L110-L146","documentation":"Thrown by ReadSkill (kernel/util/skill.go:128) when filelock.ReadFile fails on SkillsDir()/<name>/SKILL.md, meaning the named skill directory or its SKILL.md does not exist. It fires only after validateSkillName passes, so the name is well-formed but no installed skill matches it.","triggerScenarios":"Calling ReadSkill with a name that was never installed; the skill directory exists but SKILL.md inside it was deleted/renamed; the skill was removed between a DiscoverSkills listing and the ReadSkill call.","commonSituations":"UI lists stale skills after one was uninstalled elsewhere; typo in the skill name; an automation pipeline references a skill that is not provisioned on this workspace; a skill directory was partially deleted (folder remained, SKILL.md gone).","solutions":["Call DiscoverSkills() first and pick from the returned names; only ReadSkill names that appear in the list.","If the directory exists but SKILL.md is missing, restore it or remove the broken skill directory.","Treat 'skill not found' as a non-fatal, user-facing message and offer to install the skill via InstallSkill."],"exampleFix":"// before\ncontent, err := util.ReadSkill(name)\nif err != nil { return err }\n\n// after (verify existence first, degrade gracefully)\nfound := false\nfor _, s := range util.DiscoverSkills() {\n    if strings.EqualFold(s.Name, name) { found = true; break }\n}\nif !found {\n    return fmt.Errorf(\"skill %q is not installed; install it first\", name)\n}\ncontent, err := util.ReadSkill(name)","handlingStrategy":"validation","validationCode":"// Verify the skill is installed before reading\ninstalled := false\nfor _, s := range util.DiscoverSkills() {\n    if strings.EqualFold(s.Name, name) { installed = true; break }\n}\nif !installed {\n    return fmt.Errorf(\"skill %q is not installed\", name)\n}","typeGuard":null,"tryCatchPattern":"content, err := util.ReadSkill(name)\nif err != nil && strings.HasPrefix(err.Error(), \"skill not found\") {\n    // non-fatal: offer to install, or fall back to a default skill\n    return installableSkill{name: name}\n}\nif err != nil { return err }","preventionTips":["Always source skill names from DiscoverSkills() rather than hard-coding them.","Clean up partially deleted skill directories (folder without SKILL.md).","Handle 'skill not found' as a user-facing 'install it?' prompt, not a hard error."],"tags":["skill","not-found","go"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}