go-redis/redis · error
redis: FT.AGGREGATE COLLECT: ASC and DESC are mutually exclu
Error message
redis: FT.AGGREGATE COLLECT: ASC and DESC are mutually exclusive
What it means
Returned by NewCollectReducer when a SortBy entry in FTAggregateCollect has both Asc and Desc set to true. A COLLECT SORTBY field can carry at most one direction; setting both is contradictory, so the builder refuses to serialize it. This guard runs locally before any command is sent to Redis.
Source
Thrown at search_collect.go:116
default:
return nil, fmt.Errorf("redis: FT.AGGREGATE COLLECT requires FieldsAll or a non-empty Fields list")
}
// DISTINCT (optional, forward-compatible).
if o.Distinct {
args = append(args, "DISTINCT")
}
// SORTBY (optional). sort_narg counts each field plus its optional
// direction token.
if len(o.SortBy) > 0 {
sortTokens := make([]interface{}, 0, len(o.SortBy)*2)
for _, s := range o.SortBy {
if strings.TrimLeft(s.FieldName, "@") == "" {
return nil, fmt.Errorf("redis: FT.AGGREGATE COLLECT: empty field name in SortBy")
}
if s.Asc && s.Desc {
return nil, fmt.Errorf("redis: FT.AGGREGATE COLLECT: ASC and DESC are mutually exclusive")
}
sortTokens = append(sortTokens, ensureAtPrefix(s.FieldName))
switch {
case s.Desc:
sortTokens = append(sortTokens, "DESC")
case s.Asc:
sortTokens = append(sortTokens, "ASC")
// neither set: ASC is the server default; emit nothing.
}
}
args = append(args, "SORTBY", len(sortTokens))
args = append(args, sortTokens...)
}
// LIMIT (optional).
if o.Limit != nil {
args = append(args, "LIMIT", o.Limit.Offset, o.Limit.Count)
}View on GitHub (pinned to 36d97525cd)
Solutions
- Set exactly one of Asc or Desc on each SortBy entry (or leave both false for the server's ASC default).
- Audit the call site that constructs FTAggregateSortBy to ensure toggling one direction clears the other.
- If directions come from user input, coerce a single enum into Asc xor Desc before passing to NewCollectReducer.
Example fix
// before
SortBy: []FTAggregateSortBy{{FieldName: "price", Asc: true, Desc: true}}
// after
SortBy: []FTAggregateSortBy{{FieldName: "price", Desc: true}} Defensive patterns
Strategy: validation
Validate before calling
func validCollectDirections(sb []FTAggregateSortBy) error {
for i, s := range sb {
if s.Asc && s.Desc {
return fmt.Errorf("SortBy[%d]: Asc and Desc both set", i)
}
}
return nil
} Type guard
func directionIsUnique(s FTAggregateSortBy) bool { return !(s.Asc && s.Desc) } Prevention
- Expose direction as a single enum (asc/desc/none) and map it to Asc xor Desc at the boundary.
- Never default both Asc and Desc to true.
- When toggling one direction on, clear the other in the same assignment.
When it happens
Trigger: NewCollectReducer(FTAggregateCollect{SortBy: []FTAggregateSortBy{{FieldName: "price", Asc: true, Desc: true}}}), or the equivalent via AggregateBuilder.Collect. Triggered during argument building, not from a server reply.
Common situations: Defaulting both flags to true in a config struct. Reusing a FTAggregateSortBy value and flipping Desc on without clearing Asc. UI that exposes separate ASC/DESC toggles and allows both selected.
Related errors
- redis: FT.AGGREGATE COLLECT: empty field name in SortBy
- FT.AGGREGATE: Collect must follow a GroupBy step
- FT.AGGREGATE: Steps cannot be combined with the deprecated L
- FT.AGGREGATE: LOADALL and LOAD are mutually exclusive
- FT.AGGREGATE: each step must set exactly one of Load, Apply,
AI-assisted analysis of go-redis/redis@36d97525cd (2026-08-06).
Data as JSON: /data/errors/d2acf1b2772fa495.json.
Report an issue: GitHub.