redis/go-redis · error

FT.SEARCH: ASC and DESC are mutually exclusive

Error message

FT.SEARCH: ASC and DESC are mutually exclusive

What it means

go-redis validates FTSearchOptions before sending FT.SEARCH: within a single SortBy entry, Asc and Desc cannot both be true since they are mutually exclusive sort directions. This is a client-side argument-validation error raised while building the command arguments, before anything reaches the server.

Source

Thrown at search_commands.go:3403

		}
		if options.Expander != "" {
			queryArgs = append(queryArgs, "EXPANDER", options.Expander)
		}
		if options.Scorer != "" {
			queryArgs = append(queryArgs, "SCORER", options.Scorer)
		}
		if options.ExplainScore {
			queryArgs = append(queryArgs, "EXPLAINSCORE")
		}
		if options.Payload != "" {
			queryArgs = append(queryArgs, "PAYLOAD", options.Payload)
		}
		if options.SortBy != nil {
			queryArgs = append(queryArgs, "SORTBY")
			for _, sortBy := range options.SortBy {
				queryArgs = append(queryArgs, sortBy.FieldName)
				if sortBy.Asc && sortBy.Desc {
					return nil, fmt.Errorf("FT.SEARCH: ASC and DESC are mutually exclusive")
				}
				if sortBy.Asc {
					queryArgs = append(queryArgs, "ASC")
				}
				if sortBy.Desc {
					queryArgs = append(queryArgs, "DESC")
				}
			}
			if options.SortByWithCount {
				queryArgs = append(queryArgs, "WITHCOUNT")
			}
		}
		if options.LimitOffset >= 0 && options.Limit > 0 {
			queryArgs = append(queryArgs, "LIMIT", options.LimitOffset, options.Limit)
		}
		if options.Params != nil {
			queryArgs = append(queryArgs, "PARAMS", len(options.Params)*2)
			for key, value := range options.Params {

View on GitHub (pinned to c5cad058c7)

Solutions

  1. Set only one direction flag per SortBy entry: Asc: true or Desc: true, not both
  2. Leave both false to use the server default (ASC) when no explicit direction is needed
  3. If options come from user input or config, sanitize them: if both are set, prefer one (e.g. Desc) or reject the input earlier
  4. Review option-merging code so setting one flag resets the other

Example fix

// before
sortBy := redis.SearchSortBy{FieldName: "age", Asc: true, Desc: true}
// after
sortBy := redis.SearchSortBy{FieldName: "age", Desc: true}
Defensive patterns

Strategy: validation

Validate before calling

func sanitizeSortBy(sortBy []redis.SearchSortBy) []redis.SearchSortBy {
    out := make([]redis.SearchSortBy, len(sortBy))
    for i, s := range sortBy {
        if s.Asc && s.Desc { s.Desc = false } // or reject
        out[i] = s
    }
    return out
}

Type guard

func sortDirectionValid(s redis.SearchSortBy) bool {
    return !(s.Asc && s.Desc)
}

Try / catch

args, err := redis.NewFTSearchQuery("idx", query, opts).Args() // or call FTSearch
if err != nil {
    if strings.Contains(err.Error(), "mutually exclusive") {
        return fmt.Errorf("bad sort config: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: Calling FTSearch (or FTSearchWithArgs) with options.SortBy where one SearchSortBy has both Asc: true and Desc: true set.

Common situations: Copy-paste or programmatic construction of sort options where both flags are set from separate booleans; merging user-supplied options without clearing the opposite flag; misunderstanding that the zero value (neither set) is the default ASC.

Related errors


AI-assisted analysis of redis/go-redis@c5cad058c7 (2026-09-01). Data as JSON: /api/errors/dfb343fa23c1a1fc. Report an issue: GitHub.