{"id":"1db6c00377086514","repo":"go-redis/redis","slug":"redis-ft-cursor-command-requires-at-least-3-argum","errorCode":null,"errorMessage":"redis: FT.CURSOR command requires at least 3 arguments","messagePattern":"redis: FT\\.CURSOR command requires at least 3 arguments","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"osscluster_router.go","lineNumber":18,"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","sourceCodeStart":1,"sourceCodeEnd":36,"githubUrl":"https://github.com/go-redis/redis/blob/36d97525cd8076aed67cddf54778e9ea84550929/osscluster_router.go#L1-L36","documentation":"Returned internally by ClusterClient.executeCursorCommand when an FT.CURSOR command has fewer than 4 argument slots. The router needs at least [\"FT.CURSOR\", <action>, <index>, <cursorId>] to determine sticky routing. This is an internal guard; the public FTCursorRead/FTCursorDel builders always supply enough args, so hitting it means a malformed or manually-constructed FT.CURSOR Cmder reached the cluster router.","triggerScenarios":"Manually constructing a Cmder with too few args whose Name() is \"ft.cursor\" and routing it through a ClusterClient. Using a low-level/custom command path that builds \"FT.CURSOR\" with fewer than 4 args. Not triggered by the documented FTCursorRead/FTCursorDel helpers.","commonSituations":"Building custom command wrappers that strip or omit arguments. A bug in a command builder that passes nil/empty index or cursorId. Interop code that re-wraps commands for cluster routing without preserving the full arg list.","solutions":["Use the provided FTCursorRead(ctx, index, cursorId, count) or FTCursorDel(ctx, index, cursorId) helpers, which always supply a complete argument list.","If building a custom FT.CURSOR Cmder, ensure args contains at least: \"FT.CURSOR\", the action string, the index, and the cursor ID (4 elements).","Check for nil/empty values in the index or cursorId before constructing the command."],"exampleFix":"// before — manually built command missing args\ncmd := redis.NewStatusCmd(ctx, \"FT.CURSOR\", \"DEL\") // too few args\nclusterClient.Process(ctx, cmd)\n\n// after — use the typed helper\nstatus := clusterClient.FTCursorDel(ctx, \"myIndex\", cursorId)","handlingStrategy":"validation","validationCode":"func validateFTCursorArgs(args []interface{}) error {\n    if len(args) < 4 {\n        return errors.New(\"FT.CURSOR requires at least [FT.CURSOR, action, index, cursorId]\")\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"if err := cmd.Err(); err != nil {\n    if errors.Is(err, redis.ErrNothingFound) {/*...*/}\n    if err.Error() == \"redis: FT.CURSOR command requires at least 3 arguments\" {\n        // command was constructed with too few args; use the typed builder\n    }\n}","preventionTips":["Prefer the typed FTCursorRead/FTCursorDel builders over hand-constructed Cmder args.","Never strip or omit arguments when wrapping FT.CURSOR commands for cluster routing.","Unit-test custom command builders against a ClusterClient to catch arg-count regressions."],"tags":["cluster","search","ft-cursor","router","internal"],"analyzedSha":"36d97525cd8076aed67cddf54778e9ea84550929","analyzedAt":"2026-08-06T01:08:27.376Z","schemaVersion":2}