gastownhall/beads · error

duplicate custom status name %q

Error message

duplicate custom status name %q

What it means

When parsing custom statuses (from config or flags), each name is checked against a 'seen' set. If the same name appears twice, validation fails because custom status names must be unique. This prevents ambiguous status semantics downstream.

Source

Thrown at internal/types/types.go:650

			category = StatusCategory(catStr)
			if !validCategories[category] {
				return nil, fmt.Errorf("invalid category %q for status %q: must be one of active, wip, done, frozen", catStr, name)
			}
		} else {
			name = part
			category = CategoryUnspecified
		}

		if !statusNameRegexp.MatchString(name) {
			return nil, fmt.Errorf("invalid status name %q: must match [a-z][a-z0-9_-]* (lowercase, letter-first, no spaces)", name)
		}

		if builtInStatusNames[strings.ToLower(name)] {
			return nil, fmt.Errorf("custom status %q collides with built-in status", name)
		}

		if seen[name] {
			return nil, fmt.Errorf("duplicate custom status name %q", name)
		}
		seen[name] = true

		result = append(result, CustomStatus{Name: name, Category: category})
	}

	if len(result) > maxCustomStatuses {
		return nil, fmt.Errorf("too many custom statuses (%d): maximum is %d", len(result), maxCustomStatuses)
	}

	return result, nil
}

// CustomStatusNames extracts just the name strings from a slice of CustomStatus.
// Useful for backward-compatible callers that only need names for validation.
func CustomStatusNames(statuses []CustomStatus) []string {
	if len(statuses) == 0 {
		return nil

View on GitHub (pinned to 71377f2769)

Solutions

  1. Remove the duplicate entry so each custom status name appears exactly once in the config/flag list
  2. If merging configs, deduplicate by status name before passing the list to the parser
  3. Check for JSON/YAML arrays accidentally containing the same object twice

Example fix

// before (config)
"custom_statuses": [{"name":"in-review","category":"progress"},{"name":"in-review","category":"progress"}]
// after
"custom_statuses": [{"name":"in-review","category":"progress"}]
Defensive patterns

Strategy: validation

Validate before calling

names := []string{"in-review", "in-review"}
seen := map[string]bool{}
for _, n := range names {
    if seen[n] { return fmt.Errorf("duplicate custom status %q", n) }
    seen[n] = true
}

Type guard

func hasUniqueStatusNames(statuses []CustomStatus) bool {
    seen := map[string]bool{}
    for _, s := range statuses {
        if seen[s.Name] { return false }
        seen[s.Name] = true
    }
    return true
}

Try / catch

statuses, err := ParseCustomStatuses(raw)
if errors.Is(err, ErrDuplicateStatus) || strings.Contains(err.Error(), "duplicate custom status") {
    // deduplicate and retry
}

Prevention

When it happens

Trigger: Calling the custom-status parsing function (types.go, custom status validation) with a list containing two entries with the exact same name after any normalization — e.g. a config file or --status flag listing a status twice.

Common situations: Merging config from multiple sources (user config + project config) where both define the same custom status; copy-pasting a status block; scripted config generation that appends duplicates.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/5d2daa4379e3e4ee. Report an issue: GitHub.