{"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":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/util/skill.go","lineNumber":846,"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":828,"sourceCodeEnd":864,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/util/skill.go#L828-L864","documentation":"installSkillDirs unzips candidate skill directories and copies each one containing a valid SKILL.md into the workspace skills directory. When every candidate is skipped (unreadable SKILL.md, missing or invalid 'name' frontmatter), result.Names stays empty and the installer fails with 'no valid skill installed' rather than succeeding silently.","triggerScenarios":"Calling InstallSkill with a zip whose SKILL.md files are all unreadable, all lack a 'name' frontmatter field at archive root, or all have names rejected by validateSkillName.","commonSituations":"Installing a skill archive exported without frontmatter; a zip with a single SKILL.md at the root missing 'name'; an archive of plain markdown notes; corrupted zip entries; name validation failures after renaming skills.","solutions":["Open the zip and add a valid 'name: <skill-name>' field to the YAML frontmatter of every SKILL.md","Ensure SKILL.md sits in a subdirectory (not archive root) if there is no name field, so the directory name can be used as fallback","Verify each skill name passes validateSkillName (valid identifier-like characters)","Regenerate/re-download the archive and confirm SKILL.md files are readable and non-empty"],"exampleFix":"// before\n---\ndescription: My skill\n---\n\n// after\n---\nname: my-skill\ndescription: My skill\n---\n","handlingStrategy":"validation","validationCode":"const md = await zip.file('**/SKILL.md').async('string');\nconst fm = md.match(/^---\\n([\\s\\S]*?)\\n---/);\nif (!fm || !/^name:\\s*\\S+/m.test(fm[1])) throw new Error('archive SKILL.md missing name frontmatter');","typeGuard":null,"tryCatchPattern":"try {\n  await api.installSkill(zipData);\n} catch (e) {\n  if (e.message.includes('no valid skill installed')) {\n    // inspect archive SKILL.md frontmatter and fix before retrying\n  }\n}","preventionTips":["Always include a 'name' field in SKILL.md frontmatter when packaging zips","Test-install every archive you distribute","Keep skill names within validateSkillName rules","Avoid shipping raw markdown collections as skill archives"],"tags":["skill-install","frontmatter","validation","go"],"backgroundTag":"schema-validation-failed","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"}