{"record":{"id":"b6b5e9c5b7aaec41","repo":"junegunn/fzf","slug":"duplicate-sort-criteria-name","errorCode":null,"errorMessage":"duplicate sort criteria: ${name}","messagePattern":"duplicate sort criteria: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/options.go","lineNumber":1359,"sourceCode":"\tcase \"path\":\n\t\treturn str, []criterion{byScore, byPathname, byLength}, nil\n\tcase \"default\":\n\t\treturn str, []criterion{byScore, byLength}, nil\n\t}\n\treturn str, nil, errors.New(\"invalid scoring scheme: \" + str + \" (expected: default|path|history)\")\n}\n\nfunc parseTiebreak(str string) ([]criterion, error) {\n\tcriteria := []criterion{byScore}\n\thasIndex := false\n\thasChunk := false\n\thasLength := false\n\thasBegin := false\n\thasEnd := false\n\thasPathname := false\n\tcheck := func(notExpected *bool, name string) error {\n\t\tif *notExpected {\n\t\t\treturn errors.New(\"duplicate sort criteria: \" + name)\n\t\t}\n\t\tif hasIndex {\n\t\t\treturn errors.New(\"index should be the last criterion\")\n\t\t}\n\t\t*notExpected = true\n\t\treturn nil\n\t}\n\tfor _, str := range strings.Split(strings.ToLower(str), \",\") {\n\t\tswitch str {\n\t\tcase \"index\":\n\t\t\tif err := check(&hasIndex, \"index\"); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\tcase \"chunk\":\n\t\t\tif err := check(&hasChunk, \"chunk\"); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tcriteria = append(criteria, byChunk)","sourceCodeStart":1341,"sourceCodeEnd":1377,"githubUrl":"https://github.com/junegunn/fzf/blob/bd4efa277b49ef34ca4025bff9b1a288e1980eff/src/options.go#L1341-L1377","documentation":"parseTiebreak validates the --tiebreak option (comma-separated secondary sort criteria after score). Each criterion may appear at most once; the shared check() closure returns 'duplicate sort criteria: <name>' when the boolean flag for that criterion was already set by an earlier token.","triggerScenarios":"--tiebreak length,length; --tiebreak begin,chunk,begin; --tiebreak index,length,index — any repeated token among index|chunk|length|begin|end|pathname.","commonSituations":"Appending to an existing FZF_DEFAULT_OPTS string that already contains --tiebreak (flags concatenate); copy-paste accumulation in shell rc files; generated tiebreak lists without dedup.","solutions":["List each criterion once: --tiebreak length,begin","Deduplicate before passing: --tiebreak \"$(echo \"$TB\" | tr ',' '\\n' | awk '!seen[$0]++' | paste -sd,)\"","Audit FZF_DEFAULT_OPTS and wrapper scripts for stacked --tiebreak flags","Remember score is always first implicitly — never add it yourself"],"exampleFix":"# before\nexport FZF_DEFAULT_OPTS='--tiebreak length --tiebreak length,end'\n# after\nexport FZF_DEFAULT_OPTS='--tiebreak length,end'","handlingStrategy":"validation","validationCode":"TB='length,begin'\nIFS=',' read -ra C <<< \"$(tr 'A-Z' 'a-z' <<< \"$TB\")\"\nDUP=$(printf '%s\\n' \"${C[@]}\" | sort | uniq -d)\n[ -z \"$DUP\" ] || { echo \"duplicate tiebreaks: $DUP\" >&2; exit 1; }\nfzf --tiebreak \"$TB\"","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Deduplicate the criteria list before passing it","Keep a single source of truth for --tiebreak; do not stack it in both FZF_DEFAULT_OPTS and the CLI","Exclude 'score' — it is implicit and cannot be listed"],"tags":["fzf","options-parsing","sorting","tiebreak","go"],"backgroundTag":null,"analyzedSha":"bd4efa277b49ef34ca4025bff9b1a288e1980eff","analyzedAt":"2026-08-15T06:11:46.983Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}