{"id":"833ce8642c4824db","repo":"go-redis/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":965,"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":947,"sourceCodeEnd":983,"githubUrl":"https://github.com/go-redis/redis/blob/36d97525cd8076aed67cddf54778e9ea84550929/ring.go#L947-L983","documentation":"Returned by Ring.Watch when the keys slice is empty. A Ring transaction must lock keys on a single shard, so at least one key is required; calling Watch with no keys is a programmer error and is rejected before any shard lookup.","triggerScenarios":"Calling ring.Watch(ctx, fn) with no key arguments, or passing a keys slice that resolves to length zero (e.g. unpacking an empty slice via keys...).","commonSituations":"Dynamic key list computed at runtime that happened to be empty; refactoring that dropped the key argument; calling Watch with a pre-sliced variadic that was filtered down to nothing.","solutions":["Guard the Watch call site: only invoke Watch when len(keys) > 0.","If an empty key set is legitimately possible in your flow, handle it without a transaction (e.g. skip or use a non-transactional path).","Audit call sites that build the keys slice to ensure they never produce an empty result."],"exampleFix":"// before\nerr := ring.Watch(ctx, fn) // no keys -> error\n\n// after\nif len(keys) == 0 {\n    return errors.New(\"cannot run transaction without keys\")\n}\nerr := ring.Watch(ctx, fn, keys...)","handlingStrategy":"validation","validationCode":"if len(keys) == 0 {\n    return errors.New(\"Ring.Watch requires at least one key\")\n}\nreturn ring.Watch(ctx, fn, keys...)","typeGuard":null,"tryCatchPattern":"if err := ring.Watch(ctx, fn, keys...); err != nil {\n    if strings.Contains(err.Error(), \"requires at least one key\") {\n        // programmer error: fix the call site\n    }\n}","preventionTips":["Never call Watch with a variadic slice that may be empty without a length check.","Add a lint/test asserting Watch call sites always pass >= 1 key.","Centralize transaction construction in a helper that enforces the invariant."],"tags":["ring","transaction","validation","api-misuse"],"analyzedSha":"36d97525cd8076aed67cddf54778e9ea84550929","analyzedAt":"2026-08-06T01:08:27.376Z","schemaVersion":2}