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

  1. Use the provided FTCursorRead(ctx, index, cursorId, count) or FTCursorDel(ctx, index, cursorId) helpers, which always supply a complete argument list.
  2. 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).
  3. 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

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


AI-assisted analysis of go-redis/redis@36d97525cd (2026-08-06). Data as JSON: /data/errors/1db6c00377086514.json. Report an issue: GitHub.