larksuite/cli · error
%s: skill %q is missing SKILL.md
Error message
%s: skill %q is missing SKILL.md
What it means
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.
Source
Thrown at internal/skillpolicy/resolver.go:188
}
entries, err := fs.ReadDir(source, ".")
if err != nil {
return snapshot, fmt.Errorf("%s: cannot read root: %w", label, err)
}
for _, e := range entries {
name := e.Name()
if !e.IsDir() {
return snapshot, fmt.Errorf("%s: %q is not a directory; every %s entry must be a <skill>/ dir", label, name, label)
}
if !isSkillName(name) {
return snapshot, fmt.Errorf("%s: %q is not a valid skill name", label, name)
}
ok, err := skillExists(source, name)
if err != nil {
return snapshot, fmt.Errorf("%s: probing skill %q: %w", label, name, err)
}
if !ok {
return snapshot, fmt.Errorf("%s: skill %q is missing SKILL.md", label, name)
}
manifest, err := readSkillManifest(source, name)
if err != nil {
return snapshot, fmt.Errorf("%s: skill %q has invalid metadata: %w", label, name, err)
}
snapshot.skills[name] = manifest
}
return snapshot, nil
}
// validateSelection rejects allow/remove entries that cannot compose against
// the already-validated base snapshot.
func validateSelection(lower skillTreeSnapshot, spec *platform.SkillsOverlay) error {
if err := validateSkillNames("Allow", spec.Allow); err != nil {
return err
}
if err := validateSkillNames("Remove", spec.Remove); err != nil {
return errView on GitHub (pinned to 7fd6ef3c07)
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.
Example fix
// before (overlay embed misses manifest) //go:embed myskill/references var overlayFS embed.FS // after //go:embed myskill/SKILL.md myskill/references var overlayFS embed.FS
Defensive patterns
Strategy: validation
Validate before calling
func hasSkillManifest(fsys fs.FS, name string) error {
_, err := fs.Stat(fsys, name+"/SKILL.md")
if err != nil {
return fmt.Errorf("skill %q is missing SKILL.md", name)
}
return nil
} Try / catch
resolved, err := skillpolicy.ResolveWithReferences(base, specs)
if err != nil {
if strings.Contains(err.Error(), "missing SKILL.md") {
// surface the labeled tree and skill name from the message
}
return err
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- %s: skill %q has invalid metadata: %w
- %w; Allow/Remove require skills in the base tree
- %s: skill %q is not in the base tree
- Invalid column: {column!r}
- Invalid column index: {index}
AI-assisted analysis of larksuite/cli@7fd6ef3c07 (2026-09-04).
Data as JSON: /api/errors/2b4617aa54000173.
Report an issue: GitHub.