go-redis/redis · error

redis: invalid timeseries aggregator at index %d[%d]: %d

Error message

redis: invalid timeseries aggregator at index %d[%d]: %d

What it means

Returned by buildNRangeAggregationArgs when an inner aggregator's String() returns empty, i.e. the value is outside the defined iota range (greater than CountAll). Reports the outer index, inner index, and raw int value to locate the bad cast.

Source

Thrown at timeseries_commands.go:1391

// lists one or more aggregators for its key and is emitted as a single comma-joined wire
// token; specs for different keys are separate wire tokens.
func buildNRangeAggregationArgs(keys []string, aggregators [][]Aggregator) ([]string, error) {
	if len(aggregators) != len(keys) {
		return nil, fmt.Errorf("redis: TS.NRANGE/TS.NREVRANGE requires exactly %d aggregator spec(s), got %d", len(keys), len(aggregators))
	}
	parts := make([]string, len(aggregators))
	for i, spec := range aggregators {
		if len(spec) == 0 {
			return nil, fmt.Errorf("redis: empty timeseries aggregator spec at index %d", i)
		}
		names := make([]string, len(spec))
		for j, agg := range spec {
			if agg == Invalid {
				return nil, fmt.Errorf("redis: invalid timeseries aggregator at index %d[%d]: Invalid (%d)", i, j, agg)
			}
			s := agg.String()
			if s == "" {
				return nil, fmt.Errorf("redis: invalid timeseries aggregator at index %d[%d]: %d", i, j, agg)
			}
			names[j] = s
		}
		parts[i] = strings.Join(names, ",")
	}
	return parts, nil
}

// appendNRangeOptions appends optional TS.NRANGE / TS.NREVRANGE arguments to args.
func appendNRangeOptions(
	args []interface{},
	keys []string,
	latest bool,
	filterByTS []int,
	filterByValue []float64,
	count int,
	align interface{},
	aggregators [][]Aggregator,

View on GitHub (pinned to 36d97525cd)

Solutions

  1. Validate each aggregator is within [redis.Avg, redis.CountAll] before building specs.
  2. Use named constants instead of casting raw ints.
  3. Reject unknown aggregator ids when parsing external input.

Example fix

// before
opts := &redis.TSNRangeOptions{
    Aggregators: [][]redis.Aggregator{{redis.Aggregator(20)}},
}

// after
opts := &redis.TSNRangeOptions{
    Aggregators: [][]redis.Aggregator{{redis.Avg}},
}
Defensive patterns

Strategy: validation

Validate before calling

func validateNRangeSpecs(aggs [][]redis.Aggregator) error {
    for i, spec := range aggs {
        for j, a := range spec {
            if a < redis.Avg || a > redis.CountAll {
                return fmt.Errorf("aggregator [%d][%d] out of range: %d", i, j, a)
            }
        }
    }
    return nil
}

Type guard

func isValidAggregator(a redis.Aggregator) bool { return a >= redis.Avg && a <= redis.CountAll }

Prevention

When it happens

Trigger: Passing Aggregators containing an Aggregator produced by an unchecked cast such as redis.Aggregator(20), or deserialised from an out-of-range index.

Common situations: Mapping an external/config aggregator id into the Aggregator type without bounds-checking, or a lookup table with a wrong ordinal.

Related errors


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