{"record":{"id":"1f78ff167342ab98","repo":"redis/go-redis","slug":"redis-watch-requires-at-least-one-key","errorCode":null,"errorMessage":"redis: Watch requires at least one key","messagePattern":"redis: Watch requires at least one key","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ring.go","lineNumber":989,"sourceCode":"\n\t\t\tif err = hook(ctx, cmds); err != nil {\n\t\t\t\terrs <- err\n\t\t\t}\n\t\t}(hash, cmds)\n\t}\n\n\twg.Wait()\n\tclose(errs)\n\n\tif err := <-errs; err != nil {\n\t\treturn err\n\t}\n\treturn cmdsFirstErr(cmds)\n}\n\nfunc (c *Ring) Watch(ctx context.Context, fn func(*Tx) error, keys ...string) error {\n\tif len(keys) == 0 {\n\t\treturn fmt.Errorf(\"redis: Watch requires at least one key\")\n\t}\n\n\tvar shards []*ringShard\n\n\tfor _, key := range keys {\n\t\tif key != \"\" {\n\t\t\tshard, err := c.sharding.GetByKey(key)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tshards = append(shards, shard)\n\t\t}\n\t}\n\n\tif len(shards) == 0 {\n\t\treturn fmt.Errorf(\"redis: Watch requires at least one shard\")\n\t}","sourceCodeStart":971,"sourceCodeEnd":1007,"githubUrl":"https://github.com/redis/go-redis/blob/c5cad058c72f58370553b48566302303cf8a2e89/ring.go#L971-L1007","documentation":"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.","triggerScenarios":"Calling ringClient.Watch(ctx, fn) with an empty keys variadic, e.g. passing a nil or empty keys slice at ring.go:989.","commonSituations":"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.","solutions":["Guard before calling: return early or skip the transaction if len(keys) == 0","Fix the upstream code that produces an empty key list","If zero keys is legitimate, use the non-transactional API instead of Watch"],"exampleFix":"// before\nring.Watch(ctx, func(tx *redis.Tx) error { ... }, keys...) // keys empty\n// after\nif len(keys) == 0 {\n    return nil\n}\nreturn ring.Watch(ctx, func(tx *redis.Tx) error { ... }, keys...)","handlingStrategy":"validation","validationCode":"func watchKeys(keys ...string) ([]string, error) {\n    if len(keys) == 0 {\n        return nil, errors.New(\"Watch: no keys provided\")\n    }\n    return keys, nil\n}","typeGuard":null,"tryCatchPattern":"if err := ring.Watch(ctx, fn, keys...); err != nil {\n    if strings.Contains(err.Error(), \"requires at least one key\") {\n        return nil // treat as no-op\n    }\n    return err\n}","preventionTips":["Guard len(keys) before building transactions","Avoid passing variadic slices straight from dynamic sources","Use the non-transactional API when no keys are involved"],"tags":["go","ring","transactions","validation"],"backgroundTag":"empty-keys-argument","analyzedSha":"c5cad058c72f58370553b48566302303cf8a2e89","analyzedAt":"2026-09-01T06:50:53.388Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}