{"record":{"id":"0ef05f07f68da7b1","repo":"siyuan-note/siyuan","slug":"no-valid-skill-installed","errorCode":null,"errorMessage":"no valid skill installed","messagePattern":"no valid skill installed","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/util/skill.go","lineNumber":542,"sourceCode":"\t\tif err := os.MkdirAll(SkillsDir(), 0755); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\t// 覆盖式安装：先清旧目录\n\t\tif gulu.File.IsExist(destDir) {\n\t\t\tos.RemoveAll(destDir)\n\t\t}\n\t\tif err := filelock.Copy(srcDir, destDir); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"install skill %s failed: %s\", name, err)\n\t\t}\n\t\tresult.Names = append(result.Names, name)\n\t\tdesc := fm[\"description\"]\n\t\tif desc == \"\" {\n\t\t\tdesc = firstLine(body)\n\t\t}\n\t\tresult.Descriptions = append(result.Descriptions, desc)\n\t}\n\tif len(result.Names) == 0 {\n\t\treturn nil, errors.New(\"no valid skill installed\")\n\t}\n\treturn result, nil\n}\n\n// installFromSingleSkillMD 把单个 SKILL.md 文本内容落地为一个 skill\nfunc installFromSingleSkillMD(data []byte) (*InstallSkillResult, error) {\n\tcontent := string(data)\n\tfm, body := parseSkillFrontmatter(content)\n\tname := fm[\"name\"]\n\tif name == \"\" {\n\t\treturn nil, errors.New(\"SKILL.md frontmatter missing 'name' field\")\n\t}\n\tif err := validateSkillName(name); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := SaveSkill(name, content); err != nil {\n\t\treturn nil, err\n\t}","sourceCodeStart":524,"sourceCodeEnd":560,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/util/skill.go#L524-L560","documentation":"Thrown by installSkillDirs when every candidate skill in the archive was skipped, leaving zero installed. Skips happen when SKILL.md cannot be read, the frontmatter lacks 'name' (root case), or the name fails validation. The loop logs each skip but ultimately fails the install.","triggerScenarios":"An archive whose only SKILL.md sits at the root with no 'name' frontmatter (skipped because the root temp dir name cannot be used); all skills have invalid names (slashes, traversal); SKILL.md files are unreadable.","commonSituations":"A root-level SKILL.md missing the 'name:' field; skill directories named with characters validateSkillName rejects; permission errors reading all SKILL.md files in the zip.","solutions":["Add a 'name:' field to the frontmatter of each SKILL.md, especially any at the archive root.","Place each skill in a named subdirectory so the directory name can backstop a missing frontmatter name.","Ensure skill names pass validateSkillName (no slashes, traversal, or '.'/'..' literals)."],"exampleFix":"// before — SKILL.md at archive root with no name\n---\ndescription: a skill\n---\n...\n\n// after — add the name field\n---\nname: my-skill\ndescription: a skill\n---\n...","handlingStrategy":"validation","validationCode":"// validate each SKILL.md in the source has a usable name before packaging\nfor _, md := range skillFiles {\n    fm, _ := parseFrontmatter(md)\n    if fm[\"name\"] == \"\" && !hasParentDir(md) {\n        return errors.New(\"root SKILL.md must declare a 'name' frontmatter field\")\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always include a 'name:' field in SKILL.md frontmatter.","Put each skill in its own named subdirectory so the directory name can backstop a missing name.","Avoid names containing slashes or path traversal characters."],"tags":["skill-management","zip","manifest"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}