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.

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)

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: /data/errors/8f624a9b78b6563d.json. Report an issue: GitHub.