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
- Deduplicate the index so each skill name appears exactly once, keeping the entry with the newest digest
- Check the index generation pipeline for accidental double-listing or merge artifacts
- If names collide after trimming/normalization, rename one of the skills to a distinct kebab-case name
- 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
- Dedupe entries by trimmed name in the index generation pipeline
- Fix merge conflicts in index files by keeping one entry per skill
- Avoid whitespace-only name variants that normalize to the same key
- Add a uniqueness check to any custom index CI before publishing
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
- invalid skill name %q
- skill %s is not a complete v0.2 archive entry
- malformed config
- app registration failed: response missing device_code
- json pointer must start with '/' or be empty, got %q
AI-assisted analysis of larksuite/cli@7fd6ef3c07 (2026-09-04).
Data as JSON: /api/errors/f26c3a748520fc03.
Report an issue: GitHub.