go-redis/redis · error
redis: FT.AGGREGATE COLLECT: empty field name in Fields
Error message
redis: FT.AGGREGATE COLLECT: empty field name in Fields
What it means
Returned from NewCollectReducer (and surfaced via Collect/Run) when a field name in FTAggregateCollect.Fields is empty after stripping leading '@' characters. COLLECT requires concrete field names to project; an empty/whitespace/'@' entry cannot be rendered as @<name>.
Source
Thrown at search_collect.go:94
return "@" + strings.TrimLeft(name, "@")
}
// buildCollectArgs renders a FTAggregateCollect into the reducer argument
// token list (everything after "REDUCE COLLECT <narg>", excluding AS <alias>).
// The serializer computes <narg> as len(args), which matches the COLLECT
// contract: narg counts every FIELDS/DISTINCT/SORTBY/LIMIT token.
func buildCollectArgs(o FTAggregateCollect) ([]interface{}, error) {
args := make([]interface{}, 0, 8)
// FIELDS (required): either * or a counted list of @-names.
switch {
case o.FieldsAll:
args = append(args, "FIELDS", "*")
case len(o.Fields) > 0:
args = append(args, "FIELDS", len(o.Fields))
for _, f := range o.Fields {
if strings.TrimLeft(f, "@") == "" {
return nil, fmt.Errorf("redis: FT.AGGREGATE COLLECT: empty field name in Fields")
}
args = append(args, ensureAtPrefix(f))
}
default:
return nil, fmt.Errorf("redis: FT.AGGREGATE COLLECT requires FieldsAll or a non-empty Fields list")
}
// DISTINCT (optional, forward-compatible).
if o.Distinct {
args = append(args, "DISTINCT")
}
// SORTBY (optional). sort_narg counts each field plus its optional
// direction token.
if len(o.SortBy) > 0 {
sortTokens := make([]interface{}, 0, len(o.SortBy)*2)
for _, s := range o.SortBy {
if strings.TrimLeft(s.FieldName, "@") == "" {View on GitHub (pinned to 36d97525cd)
Solutions
- Filter the Fields slice to drop entries that are empty or contain only '@' before constructing FTAggregateCollect.
- Validate each field name with strings.TrimSpace(strings.TrimLeft(f, "@")) != "" before use.
- Fix the upstream producer so it never yields empty field names.
Example fix
// before
collect := redis.FTAggregateCollect{Fields: []string{"@sku", "", "@price"}}
b.GroupBy("@order").Collect(collect)
// after
fields := []string{"@sku", "@price"}
collect := redis.FTAggregateCollect{Fields: fields}
b.GroupBy("@order").Collect(collect) Defensive patterns
Strategy: validation
Validate before calling
for _, f := range collect.Fields {
if strings.TrimSpace(strings.TrimLeft(f, "@")) == "" {
return fmt.Errorf("empty field name in COLLECT.Fields: %q", f)
}
} Try / catch
if _, err := redis.NewCollectReducer(collect); err != nil {
if strings.Contains(err.Error(), "empty field name") {
// sanitize the Fields slice and retry
}
} Prevention
- Sanitize dynamic field lists at the boundary where they are produced.
- Default to FieldsAll=true when the field set is not known in advance.
- Add a unit test that builds FTAggregateCollect from representative inputs.
When it happens
Trigger: Passing FTAggregateCollect{Fields: []string{"", "@"}} or any entry whose strings.TrimLeft(f, "@") is empty. The check fires at reducer construction (NewCollectReducer) or via AggregateBuilder.Collect.
Common situations: Field list built dynamically from struct tags/JSON that produced empty strings; a stray "@" placeholder; trailing empty element from a strings.Split on a trailing delimiter; copy-paste leaving an empty slot.
Related errors
- FT.AGGREGATE: Collect must follow a GroupBy step
- redis: FT.AGGREGATE COLLECT requires FieldsAll or a non-empt
- FT.AGGREGATE: Reduce must follow a GroupBy step
- FT.AGGREGATE: ReduceAs must follow a GroupBy step
- FT.AGGREGATE: SortByMax must follow a SortBy step
AI-assisted analysis of go-redis/redis@36d97525cd (2026-08-06).
Data as JSON: /data/errors/069468ab5881aeb8.json.
Report an issue: GitHub.