charmbracelet/crush · warning

errors.New(s.Error)

Error message

errors.New(s.Error)

What it means

Not a distinct error string but a re-wrapping site: when a skill's discovery state carries a non-empty Error string, the workspace converts it into a Go error via errors.New(s.Error) and attaches it to the SkillState. The actual message text comes from the skill loader/discovery layer.

Source

Thrown at internal/workspace/client_workspace.go:1389

	}
}

// protoToSkillStates reconstructs internal skill state slices from
// their wire representation. Non-empty Error strings are turned into
// synthetic error values; the TUI never type-asserts on Err.
func protoToSkillStates(in []proto.SkillState) []*skills.SkillState {
	if len(in) == 0 {
		return nil
	}
	out := make([]*skills.SkillState, len(in))
	for i, s := range in {
		state := &skills.SkillState{
			Name:  s.Name,
			Path:  s.Path,
			State: skills.DiscoveryState(s.State),
		}
		if s.Error != "" {
			state.Err = errors.New(s.Error)
		}
		out[i] = state
	}
	return out
}

func todosToProto(todos []session.Todo) []proto.Todo {
	if len(todos) == 0 {
		return nil
	}
	out := make([]proto.Todo, len(todos))
	for i, t := range todos {
		out[i] = proto.Todo{
			Content:    t.Content,
			Status:     string(t.Status),
			ActiveForm: t.ActiveForm,
		}
	}

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Read state.Err's message to find the failing skill and fix its SKILL.md/frontmatter
  2. Verify the skill path exists and is readable
  3. Re-run the skills command after fixing; disable the broken skill if it's third-party

Example fix

// before
if s.Error != "" {
	state.Err = errors.New(s.Error)
}
// after
if s.Error != "" {
	state.Err = fmt.Errorf("skill %q: %w", s.Name, errors.New(s.Error))
}
Defensive patterns

Strategy: type-guard

Validate before calling

for _, s := range skillStates {
	if s.Error != "" {
		log.Printf("skill %s failed discovery: %s", s.Name, s.Error)
	}
}

Type guard

func (st *skills.SkillState) HasError() bool { return st.Err != nil }

Try / catch

if state.Err != nil {
	var skillErr *os.PathError
	if errors.As(state.Err, &skillErr) {
		// handle missing/unreadable skill file
	}
}

Prevention

When it happens

Trigger: Listing/updating skills where one or more skill files failed discovery — malformed frontmatter, unreadable path, invalid SKILL.md — so s.Error is populated and gets converted here.

Common situations: Skill file with broken YAML frontmatter; skill directory removed after registration (permission denied); a skill schema mismatch after a Crush version upgrade.

Related errors


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