{"id":"75f0b59ec89bb9ea","repo":"go-redis/redis","slug":"redis-invalid-cursor-id-type","errorCode":null,"errorMessage":"redis: invalid cursor ID type","messagePattern":"redis: invalid cursor ID type","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"osscluster_router.go","lineNumber":19,"sourceCode":"package redis\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/redis/go-redis/v9/internal/hashtag\"\n\t\"github.com/redis/go-redis/v9/internal/routing\"\n)\n\nvar (\n\terrInvalidCmdPointer         = errors.New(\"redis: invalid command pointer\")\n\terrNoCmdsToAggregate         = errors.New(\"redis: no commands to aggregate\")\n\terrNoResToAggregate          = errors.New(\"redis: no results to aggregate\")\n\terrInvalidCursorCmdArgsCount = errors.New(\"redis: FT.CURSOR command requires at least 3 arguments\")\n\terrInvalidCursorIdType       = errors.New(\"redis: invalid cursor ID type\")\n)\n\n// slotResult represents the result of executing a command on a specific slot\ntype slotResult struct {\n\tcmd  Cmder\n\tkeys []string\n\terr  error\n}\n\n// routeAndRun routes a command to the appropriate cluster nodes and executes it\nfunc (c *ClusterClient) routeAndRun(ctx context.Context, cmd Cmder, node *clusterNode) error {\n\tvar policy *routing.CommandPolicy\n\tif c.cmdInfoResolver != nil {\n\t\tpolicy = c.cmdInfoResolver.GetCommandPolicy(ctx, cmd)\n\t}\n\n\t// Set stepCount from cmdInfo if not already set","sourceCodeStart":1,"sourceCodeEnd":37,"githubUrl":"https://github.com/go-redis/redis/blob/36d97525cd8076aed67cddf54778e9ea84550929/osscluster_router.go#L1-L37","documentation":"Returned internally by ClusterClient.executeCursorCommand when the 4th argument (the cursor ID, args[3]) is not a string. The router casts args[3] to string to compute hashtag.Slot for sticky routing. The public FTCursorRead/FTCursorDel helpers pass an int cursorId which go-redis serializes to a string arg, so this guard catches a Cmder whose cursorId argument was constructed as a non-string type at a low level.","triggerScenarios":"A manually-constructed FT.CURSOR Cmder where args[3] is a non-string type (e.g. an int passed directly without serialization, or a nil). A custom command path that bypasses the normal arg serialization. Not produced by the documented FTCursorRead/FTCursorDel builders.","commonSituations":"Custom interop code building FT.CURSOR commands with raw int cursor IDs instead of letting the builder serialize them. Marshalling/buffering layers that change argument types. Bugs in test harnesses that hand-craft Cmder args.","solutions":["Use FTCursorRead(ctx, index, cursorId, count) or FTCursorDel(ctx, index, cursorId) so the cursor ID is serialized correctly.","If constructing the Cmder manually, pass the cursor ID as a string (e.g. strconv.Itoa(cursorId)) in args[3].","Verify no intermediate layer is mutating command argument types before the cluster router sees them."],"exampleFix":"// before — cursor ID passed as a non-string at a low level\ncmd := redis.NewStatusCmd(ctx, \"FT.CURSOR\", \"READ\", \"idx\", 42) // int, not string-serialized\n\n// after — use the typed helper (serializes cursorId correctly)\ncmd := client.FTCursorRead(ctx, \"idx\", 42, 10)","handlingStrategy":"validation","validationCode":"func validateFTCursorId(args []interface{}) error {\n    if len(args) < 4 {\n        return errors.New(\"not enough args\")\n    }\n    if _, ok := args[3].(string); !ok {\n        return fmt.Errorf(\"cursor ID at args[3] must be a string, got %T\", args[3])\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use the typed FTCursorRead/FTCursorDel helpers so the cursor ID is serialized correctly.","When building FT.CURSOR Cmders manually, pass the cursor ID as a string.","Ensure no marshalling layer mutates argument types before the cluster router sees them."],"tags":["cluster","search","ft-cursor","router","type-error","internal"],"analyzedSha":"36d97525cd8076aed67cddf54778e9ea84550929","analyzedAt":"2026-08-06T01:08:27.376Z","schemaVersion":2}