go-redis/redis · error

FT.AGGREGATE: SortByMax must follow a SortBy step

Error message

FT.AGGREGATE: SortByMax must follow a SortBy step

What it means

Recorded (and later returned by Run) when AggregateBuilder.SortByMax is called but the most recent pipeline step is not a SORTBY step. MAX <n> is a modifier on the last SORTBY; calling it without one (or after another step type) is a misuse and the error is deferred to Run.

Solutions

  1. Call b.SortBy(field, asc) immediately before b.SortByMax(n) so the last step is the SORTBY being modified.
  2. If you need MAX on a new SORTBY, ensure the SortBy call that starts the step comes first.
  3. Add a builder-order test asserting the step preceding SortByMax is a SortBy.

Example fix

// before
b.SortByMax(10) // no SortBy step -> error

// after
b.SortBy("@price", false).SortByMax(10)
Defensive patterns

Strategy: validation

Validate before calling

// MAX modifies the last SORTBY; ensure one exists.
if len(steps) == 0 || steps[len(steps)-1].SortBy == nil {
    return errors.New("SortByMax requires a preceding SortBy")
}

Try / catch

res, err := b.Run()
if err != nil && strings.Contains(err.Error(), "SortByMax must follow a SortBy step") {
    // add SortBy before SortByMax
}

Prevention

When it happens

Trigger: Calling b.SortByMax(n) when the builder has no steps or its last step is a GroupBy/Load/Filter rather than SortBy.

Common situations: Reordered pipeline where SortByMax was placed before SortBy; refactoring removed the SortBy call but left SortByMax; chaining SortByMax after a GroupBy reducer step.

Related errors


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

Appendix: source

Thrown at search_builders.go:366

// regardless of position in the pipeline.
func (b *AggregateBuilder) SortBy(field string, asc bool) *AggregateBuilder {
	sb := FTAggregateSortBy{FieldName: field, Asc: asc, Desc: !asc}
	if n := len(b.options.Steps); n > 0 && b.options.Steps[n-1].SortBy != nil {
		b.options.Steps[n-1].SortBy.Fields = append(b.options.Steps[n-1].SortBy.Fields, sb)
		return b
	}
	b.options.Steps = append(b.options.Steps, FTAggregateStep{
		SortBy: &FTAggregateSortByStep{Fields: []FTAggregateSortBy{sb}},
	})
	return b
}

// SortByMax sets MAX <n> on the last SORTBY step. The last step must be a
// SORTBY; otherwise Run will return an error.
func (b *AggregateBuilder) SortByMax(max int) *AggregateBuilder {
	n := len(b.options.Steps)
	if n == 0 || b.options.Steps[n-1].SortBy == nil {
		b.setErr(fmt.Errorf("FT.AGGREGATE: SortByMax must follow a SortBy step"))
		return b
	}
	b.options.Steps[n-1].SortBy.Max = max
	return b
}

// Filter sets FILTER <expr>.
func (b *AggregateBuilder) Filter(expr string) *AggregateBuilder {
	b.options.Filter = expr
	return b
}

// WithCursor enables WITHCURSOR [COUNT <n>] [MAXIDLE <ms>].
func (b *AggregateBuilder) WithCursor(count, maxIdle int) *AggregateBuilder {
	b.options.WithCursor = true
	if b.options.WithCursorOptions == nil {
		b.options.WithCursorOptions = &FTAggregateWithCursor{}
	}

View on GitHub (pinned to 36d97525cd)