larksuite/cli · error

has invalid skill name

Error message

%q has invalid skill name %q

What it means

Parse splits the reference on the first "/" and checks the skill component with ValidSkillName (non-empty, not "."/"..", no "/" or "\\"). The error includes both the original reference and the offending extracted name. It guards the path component from being smuggled into the skill name.

Solutions

  1. Pass the bare skill name (plus optional "relative/path" after one slash), e.g. "auth/TOKENS.md", not "/auth" or "my\\skill".
  2. Normalize any input path first with filepath.ToSlash and strip leading "/" before Parse.
  3. Pre-validate with skillref.ValidSkillName on the first strings.Cut segment.
  4. Use a valid skill id from `skills list` output instead of a filesystem path.

Example fix

// before
ref, err := skillref.Parse("\\skills\\auth\\TOKENS.md")
// after
ref, err := skillref.Parse("auth/TOKENS.md")
Defensive patterns

Strategy: validation

Validate before calling

skill, _, _ := strings.Cut(raw, "/")
if !skillref.ValidSkillName(skill) {
    return fmt.Errorf("reference %q: invalid skill name %q", raw, skill)
}

Type guard

func parseableRef(raw string) bool {
    skill, _, _ := strings.Cut(raw, "/")
    return skillref.ValidSkillName(skill)
}

Try / catch

ref, err := skillref.Parse(raw)
if err != nil {
    return fmt.Errorf("use <skill> or <skill>/<relative/path>: %w", err)
}

Prevention

When it happens

Trigger: Parse("Auth") is fine, but Parse(""), Parse("."), Parse(".."), Parse("a/b/c" where the first segment is invalid), Parse("my\\skill"), or Parse with a leading/trailing slash-producing empty name.

Common situations: Windows backslash separators leaking from path handling; absolute paths like "/auth/TOKENS.md" yielding an empty leading name; dots used to express current-directory-relative refs; concatenating a path prefix with the skill name.

Related errors


AI-assisted analysis of larksuite/cli@7fd6ef3c07 (2026-09-04). Data as JSON: /api/errors/5e5b7ffb9ecb4a00. Report an issue: GitHub.

Appendix: source

Thrown at internal/skillref/ref.go:29

	"io/fs"
	"strings"
)

// Ref is one exact canonical or runtime skill reference. Path is relative to
// the named skill; an empty Path denotes the skill's SKILL.md.
type Ref struct {
	Skill string
	Path  string
}

// Parse parses the "name[/relative/path]" form accepted by `skills read`.
func Parse(raw string) (Ref, error) {
	if raw == "" {
		return Ref{}, fmt.Errorf("skill reference is empty")
	}
	skill, path, _ := strings.Cut(raw, "/")
	if !ValidSkillName(skill) {
		return Ref{}, fmt.Errorf("%q has invalid skill name %q", raw, skill)
	}
	if path != "" && (!fs.ValidPath(path) || path == "." || strings.Contains(path, `\`)) {
		return Ref{}, fmt.Errorf("%q has invalid relative path %q", raw, path)
	}
	if path == "" && strings.HasSuffix(raw, "/") {
		return Ref{}, fmt.Errorf("%q has an empty relative path", raw)
	}
	return Ref{Skill: skill, Path: path}, nil
}

// ValidSkillName reports whether name can identify a top-level skill
// directory. Keep this rule aligned with the skill-tree manifest validator.
func ValidSkillName(name string) bool {
	return name != "" && name != "." && name != ".." && !strings.ContainsAny(name, `/\`)
}

// String returns the canonical "name[/relative/path]" form.
func (r Ref) String() string {

View on GitHub (pinned to 7fd6ef3c07)