larksuite/cli · error

cannot read SKILL.md: %w

Error message

cannot read SKILL.md: %w

What it means

readSkillManifest reads `<skill>/SKILL.md` from the source filesystem to extract the skill's declared dependencies, and wraps any read failure as "cannot read SKILL.md". The library requires every composed skill to ship a SKILL.md manifest; a missing or unreadable one makes the skill's dependency contract unverifiable, so composition fails for that skill.

Source

Thrown at internal/skillpolicy/dependencies.go:25

	"fmt"
	"io/fs"
	"sort"
	"strings"

	"gopkg.in/yaml.v3"
)

// skillManifest is the build-integrity metadata frozen when a skill tree is
// scanned. Runtime content within the owning skill directory remains live, but
// composition must not be able to change its dependency contract after Resolve.
type skillManifest struct {
	requiredSkills []string
}

func readSkillManifest(source fs.FS, name string) (skillManifest, error) {
	data, err := fs.ReadFile(source, name+"/SKILL.md")
	if err != nil {
		return skillManifest{}, fmt.Errorf("cannot read SKILL.md: %w", err)
	}
	required, err := parseRequiredSkills(name, data)
	if err != nil {
		return skillManifest{}, err
	}
	return skillManifest{requiredSkills: required}, nil
}

// parseRequiredSkills reads only the structured hard-dependency declaration:
//
//	metadata:
//	  requires:
//	    skills: ["lark-shared"]
//
// Markdown links and prose are intentionally irrelevant. A SKILL.md without
// YAML frontmatter declares no hard dependencies; malformed frontmatter that
// purports to be structured metadata fails closed during composition.
func parseRequiredSkills(skillName string, skillMD []byte) ([]string, error) {

View on GitHub (pinned to 7fd6ef3c07)

Solutions

  1. Create or restore SKILL.md in the skill directory (with valid frontmatter)
  2. Fix the skill name/path passed to the resolver so it matches the directory containing SKILL.md
  3. Verify the fs.FS root: embed the correct directory so `name+"/SKILL.md"` resolves
  4. Check filename case matches SKILL.md exactly on case-sensitive filesystems

Example fix

// before
my-skill/
  README.md        // no SKILL.md -> "cannot read SKILL.md: ..."
// after
my-skill/
  SKILL.md         // with metadata frontmatter
  README.md
Defensive patterns

Strategy: validation

Validate before calling

// check before composing
if _, err := fs.Stat(source, name+"/SKILL.md"); err != nil {
    return fmt.Errorf("skill %q is missing SKILL.md; fix the distribution before resolving", name)
}

Try / catch

manifest, err := readSkillManifest(source, name)
if err != nil {
    var pe *fs.PathError
    if errors.As(err, &pe) && errors.Is(pe.Err, fs.ErrNotExist) {
        return fmt.Errorf("skill %q has no SKILL.md (fs root %v): %w", name, pe.Path, err)
    }
    return err
}

Prevention

When it happens

Trigger: fs.ReadFile(source, name+"/SKILL.md") returns an error: the skill directory does not contain SKILL.md, the name is misspelled, the fs.FS is rooted differently than expected, or the file exists but cannot be opened.

Common situations: A skill directory added without its SKILL.md; a typo in the skill name passed to the resolver; embedding/embed.FS path mismatch (extra or missing root prefix); a case-sensitivity mismatch (Linux) where the file is named skill.md.

Related errors


AI-assisted analysis of larksuite/cli@7fd6ef3c07 (2026-09-04). Data as JSON: /api/errors/62f0546106d9f4ea. Report an issue: GitHub.