redis/go-redis · error

redis: Watch requires at least one key

Error message

redis: Watch requires at least one key

What it means

Ring.Watch (client-side sharding) rejects a WATCH invocation with no keys, since there would be no shard to run the transaction against. It returns a plain error rather than panicking.

Source

Thrown at ring.go:989

			if err = hook(ctx, cmds); err != nil {
				errs <- err
			}
		}(hash, cmds)
	}

	wg.Wait()
	close(errs)

	if err := <-errs; err != nil {
		return err
	}
	return cmdsFirstErr(cmds)
}

func (c *Ring) Watch(ctx context.Context, fn func(*Tx) error, keys ...string) error {
	if len(keys) == 0 {
		return fmt.Errorf("redis: Watch requires at least one key")
	}

	var shards []*ringShard

	for _, key := range keys {
		if key != "" {
			shard, err := c.sharding.GetByKey(key)
			if err != nil {
				return err
			}

			shards = append(shards, shard)
		}
	}

	if len(shards) == 0 {
		return fmt.Errorf("redis: Watch requires at least one shard")
	}

View on GitHub (pinned to c5cad058c7)

Solutions

  1. Guard before calling: return early or skip the transaction if len(keys) == 0
  2. Fix the upstream code that produces an empty key list
  3. If zero keys is legitimate, use the non-transactional API instead of Watch

Example fix

// before
ring.Watch(ctx, func(tx *redis.Tx) error { ... }, keys...) // keys empty
// after
if len(keys) == 0 {
    return nil
}
return ring.Watch(ctx, func(tx *redis.Tx) error { ... }, keys...)
Defensive patterns

Strategy: validation

Validate before calling

func watchKeys(keys ...string) ([]string, error) {
    if len(keys) == 0 {
        return nil, errors.New("Watch: no keys provided")
    }
    return keys, nil
}

Try / catch

if err := ring.Watch(ctx, fn, keys...); err != nil {
    if strings.Contains(err.Error(), "requires at least one key") {
        return nil // treat as no-op
    }
    return err
}

Prevention

When it happens

Trigger: Calling ringClient.Watch(ctx, fn) with an empty keys variadic, e.g. passing a nil or empty keys slice at ring.go:989.

Common situations: Keys collected dynamically into a slice that ends up empty (cache-miss path, empty config); refactored code where keys were moved from literals to a variable.

Related errors


AI-assisted analysis of redis/go-redis@c5cad058c7 (2026-09-01). Data as JSON: /api/errors/1f78ff167342ab98. Report an issue: GitHub.