go-redis/redis · error

at least one channel is required

Error message

at least one channel is required

What it means

Ring.Subscribe panics with 'at least one channel is required' when called with zero channel arguments (ring.go:710-712). The Ring is client-side sharded, so the first channel name is used (via c.sharding.GetByKey(channels[0])) to pick the shard that owns the subscription; with no channels there is no key to route by, hence the guard. This is a programmer error and is enforced with a panic rather than an error return.

Source

Thrown at ring.go:711

		s := shard.Client.connPool.Stats()
		acc.Hits += s.Hits
		acc.Misses += s.Misses
		acc.Timeouts += s.Timeouts
		acc.TotalConns += s.TotalConns
		acc.IdleConns += s.IdleConns
	}
	return &acc
}

// Len returns the current number of shards in the ring.
func (c *Ring) Len() int {
	return c.sharding.Len()
}

// Subscribe subscribes the client to the specified channels.
func (c *Ring) Subscribe(ctx context.Context, channels ...string) *PubSub {
	if len(channels) == 0 {
		panic("at least one channel is required")
	}

	shard, err := c.sharding.GetByKey(channels[0])
	if err != nil {
		// TODO: return PubSub with sticky error
		panic(err)
	}
	return shard.Client.Subscribe(ctx, channels...)
}

// PSubscribe subscribes the client to the given patterns.
func (c *Ring) PSubscribe(ctx context.Context, channels ...string) *PubSub {
	if len(channels) == 0 {
		panic("at least one channel is required")
	}

	shard, err := c.sharding.GetByKey(channels[0])
	if err != nil {

View on GitHub (pinned to 36d97525cd)

Solutions

  1. Validate len(channels) > 0 before calling ring.Subscribe and skip/return an error when empty.
  2. Ensure your config/source for channel names always yields at least one entry, or guard the subscription step behind a feature/config check.
  3. Wrap Subscribe in a helper that returns an error on empty input instead of letting the Ring panic.

Example fix

// before
ring.Subscribe(ctx, channels...) // channels is empty -> panic

// after
if len(channels) == 0 {
    return errors.New("no channels to subscribe")
}
sub := ring.Subscribe(ctx, channels...)
Defensive patterns

Strategy: validation

Validate before calling

func subscribeRing(ctx context.Context, r *redis.Ring, channels []string) (*redis.PubSub, error) {
    if len(channels) == 0 {
        return nil, errors.New("at least one channel is required")
    }
    return r.Subscribe(ctx, channels...), nil
}

Type guard

func nonEmptyChannels(channels []string) bool {
    return len(channels) > 0
}

Try / catch

defer func() {
    if r := recover(); r != nil {
        log.Printf("ring subscribe failed: %v", r)
    }
}()
sub := r.Subscribe(ctx, channels...)

Prevention

When it happens

Trigger: Calling ring.Subscribe(ctx) with no variadic channel arguments; passing an empty []string slice; passing a slice that was built dynamically and ended up empty due to a filter/config miss.

Common situations: Dynamic channel lists sourced from config/env that came back empty; helper functions that forward a channels slice without checking length; tests that forgot to populate the channel list; conditional logic that subscribes only when a feature flag is set, but the Subscribe call still runs.

Related errors


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