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
- Call b.SortBy(field, asc) immediately before b.SortByMax(n) so the last step is the SORTBY being modified.
- If you need MAX on a new SORTBY, ensure the SortBy call that starts the step comes first.
- 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
- Chain SortBy(...).SortByMax(n) together so the dependency is visually explicit.
- Add builder-order assertions in tests.
- Treat a SortByMax ordering error as a compile-time-style bug to fix in code, not handle at runtime.
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
- FT.AGGREGATE: Reduce must follow a GroupBy step
- FT.AGGREGATE: ReduceAs must follow a GroupBy step
- FT.AGGREGATE: Collect must follow a GroupBy step
- redis: FT.AGGREGATE COLLECT: empty field name in Fields
- redis: FT.AGGREGATE COLLECT requires FieldsAll or a non-empt
AI-assisted analysis of go-redis/redis@36d97525cd (2026-08-06).
Data as JSON: /data/errors/8f624a9b78b6563d.json.
Report an issue: GitHub.