siyuan-note/siyuan · warning

skill not found: %s

Error message

skill not found: %s

What it means

Thrown by ReadSkill (kernel/util/skill.go:128) when filelock.ReadFile fails on SkillsDir()/<name>/SKILL.md, meaning the named skill directory or its SKILL.md does not exist. It fires only after validateSkillName passes, so the name is well-formed but no installed skill matches it.

Source

Thrown at kernel/util/skill.go:128

	if strings.ContainsAny(name, `/\`) {
		return fmt.Errorf("invalid skill name: %s", name)
	}
	dir := SkillsDir()
	abs := filepath.Join(dir, name)
	if !gulu.File.IsSubPath(dir, abs) {
		return fmt.Errorf("invalid skill name: %s", name)
	}
	return nil
}

func ReadSkill(name string) (string, error) {
	if err := validateSkillName(name); err != nil {
		return "", err
	}
	skillMdPath := filepath.Join(SkillsDir(), name, "SKILL.md")
	b, err := filelock.ReadFile(skillMdPath)
	if err != nil {
		return "", fmt.Errorf("skill not found: %s", name)
	}
	return string(b), nil
}

func SaveSkill(name, content string) error {
	if err := validateSkillName(name); err != nil {
		return err
	}
	dir := SkillsDir()
	if err := os.MkdirAll(dir, 0755); err != nil {
		return err
	}
	skillDir := filepath.Join(dir, name)
	if err := os.MkdirAll(skillDir, 0755); err != nil {
		return err
	}
	skillMdPath := filepath.Join(skillDir, "SKILL.md")
	return filelock.WriteFile(skillMdPath, []byte(content))

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Call DiscoverSkills() first and pick from the returned names; only ReadSkill names that appear in the list.
  2. If the directory exists but SKILL.md is missing, restore it or remove the broken skill directory.
  3. Treat 'skill not found' as a non-fatal, user-facing message and offer to install the skill via InstallSkill.

Example fix

// before
content, err := util.ReadSkill(name)
if err != nil { return err }

// after (verify existence first, degrade gracefully)
found := false
for _, s := range util.DiscoverSkills() {
    if strings.EqualFold(s.Name, name) { found = true; break }
}
if !found {
    return fmt.Errorf("skill %q is not installed; install it first", name)
}
content, err := util.ReadSkill(name)
Defensive patterns

Strategy: validation

Validate before calling

// Verify the skill is installed before reading
installed := false
for _, s := range util.DiscoverSkills() {
    if strings.EqualFold(s.Name, name) { installed = true; break }
}
if !installed {
    return fmt.Errorf("skill %q is not installed", name)
}

Try / catch

content, err := util.ReadSkill(name)
if err != nil && strings.HasPrefix(err.Error(), "skill not found") {
    // non-fatal: offer to install, or fall back to a default skill
    return installableSkill{name: name}
}
if err != nil { return err }

Prevention

When it happens

Trigger: Calling ReadSkill with a name that was never installed; the skill directory exists but SKILL.md inside it was deleted/renamed; the skill was removed between a DiscoverSkills listing and the ReadSkill call.

Common situations: UI lists stale skills after one was uninstalled elsewhere; typo in the skill name; an automation pipeline references a skill that is not provisioned on this workspace; a skill directory was partially deleted (folder remained, SKILL.md gone).

Related errors


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/027d4d50d0d4d713. Report an issue: GitHub.