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
- Call DiscoverSkills() first and pick from the returned names; only ReadSkill names that appear in the list.
- If the directory exists but SKILL.md is missing, restore it or remove the broken skill directory.
- 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
- Always source skill names from DiscoverSkills() rather than hard-coding them.
- Clean up partially deleted skill directories (folder without SKILL.md).
- Handle 'skill not found' as a user-facing 'install it?' prompt, not a hard error.
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
- invalid skill name: %s
- image attachment request limit exceeded: at most %d images a
- invalid frontend capability ID: %s
- frontend capability description is required: %s
- invalid frontend capability [%s]: %w
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/027d4d50d0d4d713.
Report an issue: GitHub.