junegunn/fzf · error

duplicate sort criteria: ${name}

Error message

duplicate sort criteria: ${name}

What it means

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.

Source

Thrown at src/options.go:1359

	case "path":
		return str, []criterion{byScore, byPathname, byLength}, nil
	case "default":
		return str, []criterion{byScore, byLength}, nil
	}
	return str, nil, errors.New("invalid scoring scheme: " + str + " (expected: default|path|history)")
}

func parseTiebreak(str string) ([]criterion, error) {
	criteria := []criterion{byScore}
	hasIndex := false
	hasChunk := false
	hasLength := false
	hasBegin := false
	hasEnd := false
	hasPathname := false
	check := func(notExpected *bool, name string) error {
		if *notExpected {
			return errors.New("duplicate sort criteria: " + name)
		}
		if hasIndex {
			return errors.New("index should be the last criterion")
		}
		*notExpected = true
		return nil
	}
	for _, str := range strings.Split(strings.ToLower(str), ",") {
		switch str {
		case "index":
			if err := check(&hasIndex, "index"); err != nil {
				return nil, err
			}
		case "chunk":
			if err := check(&hasChunk, "chunk"); err != nil {
				return nil, err
			}
			criteria = append(criteria, byChunk)

View on GitHub (pinned to bd4efa277b)

Solutions

  1. List each criterion once: --tiebreak length,begin
  2. Deduplicate before passing: --tiebreak "$(echo "$TB" | tr ',' '\n' | awk '!seen[$0]++' | paste -sd,)"
  3. Audit FZF_DEFAULT_OPTS and wrapper scripts for stacked --tiebreak flags
  4. Remember score is always first implicitly — never add it yourself

Example fix

# before
export FZF_DEFAULT_OPTS='--tiebreak length --tiebreak length,end'
# after
export FZF_DEFAULT_OPTS='--tiebreak length,end'
Defensive patterns

Strategy: validation

Validate before calling

TB='length,begin'
IFS=',' read -ra C <<< "$(tr 'A-Z' 'a-z' <<< "$TB")"
DUP=$(printf '%s\n' "${C[@]}" | sort | uniq -d)
[ -z "$DUP" ] || { echo "duplicate tiebreaks: $DUP" >&2; exit 1; }
fzf --tiebreak "$TB"

Prevention

When it happens

Trigger: --tiebreak length,length; --tiebreak begin,chunk,begin; --tiebreak index,length,index — any repeated token among index|chunk|length|begin|end|pathname.

Common situations: 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.

Related errors


AI-assisted analysis of junegunn/fzf@bd4efa277b (2026-08-15). Data as JSON: /api/errors/b6b5e9c5b7aaec41. Report an issue: GitHub.