larksuite/cli · error

duplicate skill %s

Error message

duplicate skill %s

What it means

ParseOfficialSkillsIndexJSON rejects duplicate skill names using a seen set; after all other per-entry checks pass, an entry whose trimmed name was already accepted triggers this error. Duplicate names would make installation paths and references ambiguous, so the parser fails closed.

Source

Thrown at internal/skillscheck/sync.go:120

		Skills []officialSkill `json:"skills"`
	}

	var index officialIndex
	if err := json.Unmarshal([]byte(text), &index); err != nil {
		return nil, err
	}

	seen := map[string]bool{}
	for _, skill := range index.Skills {
		candidate := strings.TrimSpace(skill.Name)
		if !skillNamePattern.MatchString(candidate) {
			return nil, fmt.Errorf("invalid skill name %q", candidate)
		}
		if skill.Type != "archive" || strings.TrimSpace(skill.URL) == "" || !digestPattern.MatchString(skill.Digest) {
			return nil, fmt.Errorf("skill %s is not a complete v0.2 archive entry", candidate)
		}
		if seen[candidate] {
			return nil, fmt.Errorf("duplicate skill %s", candidate)
		}
		seen[candidate] = true
	}

	return sortedKeys(seen), nil
}

// parseGlobalSkillsList parses the output of "npx -y skills ls -g"
func parseGlobalSkillsList(lines []string) []string {
	seen := map[string]bool{}

	for _, line := range lines {
		trimmed := strings.TrimSpace(line)

		// Skip header
		if strings.HasPrefix(trimmed, "Global Skills") {
			continue
		}

View on GitHub (pinned to 7fd6ef3c07)

Solutions

  1. Deduplicate the index so each skill name appears exactly once, keeping the entry with the newest digest
  2. Check the index generation pipeline for accidental double-listing or merge artifacts
  3. If names collide after trimming/normalization, rename one of the skills to a distinct kebab-case name
  4. Fetch the index fresh from the official source in case a cached merged copy is stale

Example fix

// before
[{"name":"crm-sync",...},{"name":" crm-sync",...}]
// after
[{"name":"crm-sync",...}]
Defensive patterns

Strategy: validation

Validate before calling

seen := map[string]bool{}
var idx struct{ Skills []struct{ Name string `json:"name"` } `json:"skills"` } `json:"skills"`
if err := json.Unmarshal(indexData, &idx); err == nil {
    for _, s := range idx.Skills {
        n := strings.TrimSpace(s.Name)
        if seen[n] { /* duplicate: dedupe before parsing */ }
        seen[n] = true
    }
}

Try / catch

skills, err := skillscheck.ParseOfficialSkillsIndexJSON(data)
if err != nil {
    if strings.Contains(err.Error(), "duplicate skill") {
        // refetch a clean index or dedupe locally before retry
    }
    return err
}

Prevention

When it happens

Trigger: fetchOfficialSkills parsing an index containing two entries with the same (trimmed) skill name, e.g. the same skill listed twice or two entries differing only in surrounding whitespace.

Common situations: An index generator bug appending an entry twice; a merge conflict in the index file resolved badly; case/whitespace variants that trim down to identical names; a mirror concatenating two index versions.

Related errors


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