{"record":{"id":"2b4617aa54000173","repo":"larksuite/cli","slug":"s-skill-q-is-missing-skill-md","errorCode":null,"errorMessage":"%s: skill %q is missing SKILL.md","messagePattern":"(.+?): skill %q is missing SKILL\\.md","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/skillpolicy/resolver.go","lineNumber":188,"sourceCode":"\t}\n\tentries, err := fs.ReadDir(source, \".\")\n\tif err != nil {\n\t\treturn snapshot, fmt.Errorf(\"%s: cannot read root: %w\", label, err)\n\t}\n\tfor _, e := range entries {\n\t\tname := e.Name()\n\t\tif !e.IsDir() {\n\t\t\treturn snapshot, fmt.Errorf(\"%s: %q is not a directory; every %s entry must be a <skill>/ dir\", label, name, label)\n\t\t}\n\t\tif !isSkillName(name) {\n\t\t\treturn snapshot, fmt.Errorf(\"%s: %q is not a valid skill name\", label, name)\n\t\t}\n\t\tok, err := skillExists(source, name)\n\t\tif err != nil {\n\t\t\treturn snapshot, fmt.Errorf(\"%s: probing skill %q: %w\", label, name, err)\n\t\t}\n\t\tif !ok {\n\t\t\treturn snapshot, fmt.Errorf(\"%s: skill %q is missing SKILL.md\", label, name)\n\t\t}\n\t\tmanifest, err := readSkillManifest(source, name)\n\t\tif err != nil {\n\t\t\treturn snapshot, fmt.Errorf(\"%s: skill %q has invalid metadata: %w\", label, name, err)\n\t\t}\n\t\tsnapshot.skills[name] = manifest\n\t}\n\treturn snapshot, nil\n}\n\n// validateSelection rejects allow/remove entries that cannot compose against\n// the already-validated base snapshot.\nfunc validateSelection(lower skillTreeSnapshot, spec *platform.SkillsOverlay) error {\n\tif err := validateSkillNames(\"Allow\", spec.Allow); err != nil {\n\t\treturn err\n\t}\n\tif err := validateSkillNames(\"Remove\", spec.Remove); err != nil {\n\t\treturn err","sourceCodeStart":170,"sourceCodeEnd":206,"githubUrl":"https://github.com/larksuite/cli/blob/7fd6ef3c07182257ce776cdc5a614e122d5bd4b3/internal/skillpolicy/resolver.go#L170-L206","documentation":"During skill-tree composition, skillpolicy.scanSkillTree probes each skill directory in the base or overlay FS for a SKILL.md manifest. If skillExists reports the file is absent, composition fails with this message labeled by the tree (\"host Base\", \"plugin Base\", or \"plugin Overlay\"). It enforces the invariant that every top-level skill directory ships a SKILL.md.","triggerScenarios":"Calling skillpolicy.ResolveWithReferences with a base fs.FS or a SkillsOverlay whose Base/Overlay FS contains a skill directory lacking SKILL.md at <name>/SKILL.md.","commonSituations":"Plugin authors build an embed.FS with only the skill's reference/asset files and forget SKILL.md; a packaging script strips markdown files; the skill directory name and file layout disagree after a rename.","solutions":["Add a valid SKILL.md to the skill directory named in the message inside the offending FS (label tells you whether it is the host base, plugin Base, or plugin Overlay).","Remove the empty/partial skill directory from the overlay if it was committed accidentally.","If you are a wrapper integrator, verify the base passed to ResolveWithReferences (via cmd.SetEmbeddedSkillContent) is the complete embedded skill tree.","Check fs.FS path casing/subdirectory nesting; embedded trees commonly need io/fs.Sub so <name>/SKILL.md exists at the expected path."],"exampleFix":"// before (overlay embed misses manifest)\n//go:embed myskill/references\nvar overlayFS embed.FS\n// after\n//go:embed myskill/SKILL.md myskill/references\nvar overlayFS embed.FS","handlingStrategy":"validation","validationCode":"func hasSkillManifest(fsys fs.FS, name string) error {\n    _, err := fs.Stat(fsys, name+\"/SKILL.md\")\n    if err != nil {\n        return fmt.Errorf(\"skill %q is missing SKILL.md\", name)\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"resolved, err := skillpolicy.ResolveWithReferences(base, specs)\nif err != nil {\n    if strings.Contains(err.Error(), \"missing SKILL.md\") {\n        // surface the labeled tree and skill name from the message\n    }\n    return err\n}","preventionTips":["Use //go:embed skilldir/SKILL.md skilldir/... (or embed the whole skill dir) so the manifest cannot be omitted","Add a unit test that walks the overlay FS and stats <name>/SKILL.md for every top-level directory","Run node scripts/skill-format-check/index.js before building plugin distributions"],"tags":["go","skill-policy","missing-manifest","plugin-overlay"],"backgroundTag":"missing-skill-manifest","analyzedSha":"7fd6ef3c07182257ce776cdc5a614e122d5bd4b3","analyzedAt":"2026-09-04T21:17:44.649Z","contentChangedAt":"2026-09-04T21:17:44.649Z","schemaVersion":2},"datasetVersion":"2026-09-12T02:17:10.037Z"}