go-redis/redis · error
redis: FT.CURSOR command requires at least 3 arguments
Error message
redis: FT.CURSOR command requires at least 3 arguments
What it means
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.
Source
Thrown at osscluster_router.go:18
package redis
import (
"context"
"errors"
"fmt"
"reflect"
"sync"
"time"
"github.com/redis/go-redis/v9/internal/hashtag"
"github.com/redis/go-redis/v9/internal/routing"
)
var (
errInvalidCmdPointer = errors.New("redis: invalid command pointer")
errNoCmdsToAggregate = errors.New("redis: no commands to aggregate")
errNoResToAggregate = errors.New("redis: no results to aggregate")
errInvalidCursorCmdArgsCount = errors.New("redis: FT.CURSOR command requires at least 3 arguments")
errInvalidCursorIdType = errors.New("redis: invalid cursor ID type")
)
// slotResult represents the result of executing a command on a specific slot
type slotResult struct {
cmd Cmder
keys []string
err error
}
// routeAndRun routes a command to the appropriate cluster nodes and executes it
func (c *ClusterClient) routeAndRun(ctx context.Context, cmd Cmder, node *clusterNode) error {
var policy *routing.CommandPolicy
if c.cmdInfoResolver != nil {
policy = c.cmdInfoResolver.GetCommandPolicy(ctx, cmd)
}
View on GitHub (pinned to 36d97525cd)
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.
Example fix
// before — manually built command missing args cmd := redis.NewStatusCmd(ctx, "FT.CURSOR", "DEL") // too few args clusterClient.Process(ctx, cmd) // after — use the typed helper status := clusterClient.FTCursorDel(ctx, "myIndex", cursorId)
Defensive patterns
Strategy: validation
Validate before calling
func validateFTCursorArgs(args []interface{}) error {
if len(args) < 4 {
return errors.New("FT.CURSOR requires at least [FT.CURSOR, action, index, cursorId]")
}
return nil
} Try / catch
if err := cmd.Err(); err != nil {
if errors.Is(err, redis.ErrNothingFound) {/*...*/}
if err.Error() == "redis: FT.CURSOR command requires at least 3 arguments" {
// command was constructed with too few args; use the typed builder
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- redis: invalid cursor ID type
- redis: tx pipeline produced no outcome
- redis: no valid results to aggregate for max operation
- redis: no valid results to aggregate for logical AND operati
- redis: no valid results to aggregate for logical OR operatio
AI-assisted analysis of go-redis/redis@36d97525cd (2026-08-06).
Data as JSON: /data/errors/1db6c00377086514.json.
Report an issue: GitHub.