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

  1. List the active skills and confirm the exact SkillFilePath string to pass (IDs are full file paths, not skill names)
  2. Run skill discovery again or fix the skill directory so the SKILL.md is discovered and validated
  3. Check the discovery state (skills.GetLatestStates) — the skill may be present but in StateError due to a validation failure
  4. 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

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


AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29). Data as JSON: /api/errors/e0b4e06708a4a041. Report an issue: GitHub.