{"record":{"id":"5d2daa4379e3e4ee","repo":"gastownhall/beads","slug":"duplicate-custom-status-name-q","errorCode":null,"errorMessage":"duplicate custom status name %q","messagePattern":"duplicate custom status name %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/types/types.go","lineNumber":650,"sourceCode":"\t\t\tcategory = StatusCategory(catStr)\n\t\t\tif !validCategories[category] {\n\t\t\t\treturn nil, fmt.Errorf(\"invalid category %q for status %q: must be one of active, wip, done, frozen\", catStr, name)\n\t\t\t}\n\t\t} else {\n\t\t\tname = part\n\t\t\tcategory = CategoryUnspecified\n\t\t}\n\n\t\tif !statusNameRegexp.MatchString(name) {\n\t\t\treturn nil, fmt.Errorf(\"invalid status name %q: must match [a-z][a-z0-9_-]* (lowercase, letter-first, no spaces)\", name)\n\t\t}\n\n\t\tif builtInStatusNames[strings.ToLower(name)] {\n\t\t\treturn nil, fmt.Errorf(\"custom status %q collides with built-in status\", name)\n\t\t}\n\n\t\tif seen[name] {\n\t\t\treturn nil, fmt.Errorf(\"duplicate custom status name %q\", name)\n\t\t}\n\t\tseen[name] = true\n\n\t\tresult = append(result, CustomStatus{Name: name, Category: category})\n\t}\n\n\tif len(result) > maxCustomStatuses {\n\t\treturn nil, fmt.Errorf(\"too many custom statuses (%d): maximum is %d\", len(result), maxCustomStatuses)\n\t}\n\n\treturn result, nil\n}\n\n// CustomStatusNames extracts just the name strings from a slice of CustomStatus.\n// Useful for backward-compatible callers that only need names for validation.\nfunc CustomStatusNames(statuses []CustomStatus) []string {\n\tif len(statuses) == 0 {\n\t\treturn nil","sourceCodeStart":632,"sourceCodeEnd":668,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/types/types.go#L632-L668","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Remove the duplicate entry so each custom status name appears exactly once in the config/flag list","If merging configs, deduplicate by status name before passing the list to the parser","Check for JSON/YAML arrays accidentally containing the same object twice"],"exampleFix":"// before (config)\n\"custom_statuses\": [{\"name\":\"in-review\",\"category\":\"progress\"},{\"name\":\"in-review\",\"category\":\"progress\"}]\n// after\n\"custom_statuses\": [{\"name\":\"in-review\",\"category\":\"progress\"}]","handlingStrategy":"validation","validationCode":"names := []string{\"in-review\", \"in-review\"}\nseen := map[string]bool{}\nfor _, n := range names {\n    if seen[n] { return fmt.Errorf(\"duplicate custom status %q\", n) }\n    seen[n] = true\n}","typeGuard":"func hasUniqueStatusNames(statuses []CustomStatus) bool {\n    seen := map[string]bool{}\n    for _, s := range statuses {\n        if seen[s.Name] { return false }\n        seen[s.Name] = true\n    }\n    return true\n}","tryCatchPattern":"statuses, err := ParseCustomStatuses(raw)\nif errors.Is(err, ErrDuplicateStatus) || strings.Contains(err.Error(), \"duplicate custom status\") {\n    // deduplicate and retry\n}","preventionTips":["Deduplicate by name when merging configs from multiple sources","Add a config linter check for repeated status names","Keep status definitions in one canonical file"],"tags":["config","validation","custom-status"],"backgroundTag":"duplicate-custom-status-name","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}