charmbracelet/crush · error
%w: %s
Error message
%w: %s
What it means
FindEffective looks up a skill by its exact SkillFilePath among the active skills and returns ErrSkillNotFound wrapped with the requested ID when no match exists. This is a sentinel-wrapped error (errors.Is friendly), signaling the caller asked for a skill ID that is not currently loaded/discovered. ReadContent propagates it when resolving a skill by ID.
Source
Thrown at internal/skills/catalog.go:71
Name: skill.Name,
Description: skill.Description,
Label: label,
Source: source,
UserInvocable: skill.UserInvocable,
})
}
return entries
}
// FindEffective returns the named skill from the given active skill
// set.
func FindEffective(active []*Skill, skillID string) (*Skill, error) {
for _, skill := range active {
if skill.SkillFilePath == skillID {
return skill, nil
}
}
return nil, fmt.Errorf("%w: %s", ErrSkillNotFound, skillID)
}
// ReadContent reads the contents of a visible skill by ID and returns
// the raw bytes along with metadata about the skill.
func ReadContent(active []*Skill, skillPaths []string, workingDir string, skillID string) ([]byte, SkillReadResult, error) {
skill, err := FindEffective(active, skillID)
if err != nil {
return nil, SkillReadResult{}, err
}
_, source := skillLabel(skillPaths, workingDir, skill)
result := SkillReadResult{
Name: skill.Name,
Description: skill.Description,
Source: source,
Builtin: skill.Builtin,
}
View on GitHub (pinned to 7944b8e522)
Solutions
- List the active skills and confirm the exact SkillFilePath string to pass (IDs are full file paths, not skill names)
- Run skill discovery again or fix the skill directory so the SKILL.md is discovered and validated
- Check the discovery state (skills.GetLatestStates) — the skill may be present but in StateError due to a validation failure
- Match the ID exactly, including case and path separators
Example fix
// before
content, err := skills.ReadContent(active, paths, dir, "my-skill")
// after — use the discovered skill's file path
for _, s := range active {
if s.Name == "my-skill" {
content, err = skills.ReadContent(active, paths, dir, s.SkillFilePath)
}
} Defensive patterns
Strategy: type-guard
Validate before calling
func skillExists(active []*skills.Skill, id string) bool {
return slices.ContainsFunc(active, func(s *skills.Skill) bool { return s.SkillFilePath == id })
} Type guard
func findSkill(active []*skills.Skill, id string) (*skills.Skill, bool) {
for _, s := range active {
if s.SkillFilePath == id { return s, true }
}
return nil, false
} Try / catch
content, result, err := skills.ReadContent(active, paths, dir, skillID)
if err != nil {
if errors.Is(err, skills.ErrSkillNotFound) {
return fmt.Errorf("unknown skill %q; pick one of the active skill paths", skillID)
}
return err
} Prevention
- Always pass SkillFilePath, never the skill's display name
- Refresh the active skill list before lookups after config or directory changes
- Inspect skills.GetLatestStates to confirm the skill discovered successfully
- Handle ErrSkillNotFound explicitly since it is a sentinel error
When it happens
Trigger: Calling skills.ReadContent (or FindEffective directly) with a skillID string that does not exactly equal any active skill's SkillFilePath — including when the skill exists but was not discovered (bad directory), was filtered out, or the caller passed a name instead of a file path.
Common situations: Typos in the skill ID passed by the model or user; skill directory renamed/moved so SkillFilePath changed; skill failed validation during discovery and is not in the active list; case mismatch in the path.
Related errors
- skill not found
- read skill %q: %w
- name is required
- name must be alphanumeric with hyphens, no leading/trailing/
- description is required
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/e0b4e06708a4a041.
Report an issue: GitHub.