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

  1. Populate exactly one of Load, Apply, GroupBy, or SortBy on each FTAggregateStep.
  2. Split a step that needs multiple stages into multiple Steps (the pipeline is ordered).
  3. Remove placeholder/empty entries from the Steps slice before calling FTAggregate.
  4. 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

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


AI-assisted analysis of go-redis/redis@36d97525cd (2026-08-06). Data as JSON: /data/errors/abe8e1d6c2ca8dea.json. Report an issue: GitHub.