go-redis/redis · error
FT.AGGREGATE: each step must set exactly one of Load, Apply,
Error message
FT.AGGREGATE: each step must set exactly one of Load, Apply, GroupBy, SortBy (got %d)
What it means
Returned by appendFTAggregateStep when a single FTAggregateStep does not set exactly one of Load, Apply, GroupBy, or SortBy (the count is reported in '%d'). Each Step must map to exactly one Redis pipeline stage, so zero or multiple set fields is ambiguous and rejected before serialization.
Source
Thrown at search_commands.go:725
// appendFTAggregateStep appends the Redis command arguments for a single
// aggregation pipeline step. Each step must set exactly one of Load, Apply,
// GroupBy or SortBy.
func appendFTAggregateStep(args []interface{}, step FTAggregateStep) ([]interface{}, error) {
set := 0
if step.Load != nil {
set++
}
if step.Apply != nil {
set++
}
if step.GroupBy != nil {
set++
}
if step.SortBy != nil {
set++
}
if set != 1 {
return args, fmt.Errorf("FT.AGGREGATE: each step must set exactly one of Load, Apply, GroupBy, SortBy (got %d)", set)
}
switch {
case step.Load != nil:
args = append(args, "LOAD")
countIdx := len(args)
args = append(args, 0)
count := 0
args = append(args, step.Load.Field)
count++
if step.Load.As != "" {
args = append(args, "AS", step.Load.As)
count += 2
}
args[countIdx] = count
case step.Apply != nil:
args = append(args, "APPLY", step.Apply.Field)
if step.Apply.As != "" {View on GitHub (pinned to 36d97525cd)
Solutions
- Populate exactly one of Load, Apply, GroupBy, or SortBy on each FTAggregateStep.
- Split a step that needs multiple stages into multiple Steps (the pipeline is ordered).
- Remove placeholder/empty entries from the Steps slice before calling FTAggregate.
- Add a unit assertion that each step sets exactly one field to catch regressions.
Example fix
// before
Steps: []FTAggregateStep{
{}, // count 0 -> error
{Apply: &FTAggregateApply{Field: "@price/2"},
GroupBy: &FTAggregateGroupBy{Fields: []string{"cat"}}}, // count 2 -> error
}
// after
Steps: []FTAggregateStep{
{GroupBy: &FTAggregateGroupBy{Fields: []string{"cat"}}},
{Apply: &FTAggregateApply{Field: "@price/2", As: "half"}},
} Defensive patterns
Strategy: validation
Validate before calling
func validateStepExactlyOne(step FTAggregateStep) error {
set := 0
if step.Load != nil { set++ }
if step.Apply != nil { set++ }
if step.GroupBy != nil { set++ }
if step.SortBy != nil { set++ }
if set != 1 {
return fmt.Errorf("step must set exactly one of Load/Apply/GroupBy/SortBy, got %d", set)
}
return nil
} Type guard
func stepIsWellFormed(step FTAggregateStep) bool {
set := 0
if step.Load != nil { set++ }
if step.Apply != nil { set++ }
if step.GroupBy != nil { set++ }
if step.SortBy != nil { set++ }
return set == 1
} Prevention
- Model each pipeline stage as its own FTAggregateStep; never bundle two stages in one step.
- Filter placeholder/empty steps out of the Steps slice before calling FTAggregate.
- Add a builder/factory that constructs steps so callers cannot leave a zero-valued step.
When it happens
Trigger: Adding an FTAggregateStep with none of Load/Apply/GroupBy/SortBy set (count 0), or with two or more set (count >= 2). Triggered while iterating options.Steps in FTAggregateQuery.
Common situations: Empty step literal {{}} left as a placeholder. Reusing a step struct and setting Apply on top of an already-set GroupBy. Conditional logic that sometimes leaves all fields nil. Misunderstanding that each Step holds exactly one stage.
Related errors
- redis: FT.AGGREGATE COLLECT: empty field name in SortBy
- redis: FT.AGGREGATE COLLECT: ASC and DESC are mutually exclu
- FT.AGGREGATE: Steps cannot be combined with the deprecated L
- FT.AGGREGATE: LOADALL and LOAD are mutually exclusive
- FT.AGGREGATE: ASC and DESC are mutually exclusive
AI-assisted analysis of go-redis/redis@36d97525cd (2026-08-06).
Data as JSON: /data/errors/abe8e1d6c2ca8dea.json.
Report an issue: GitHub.