hibiken/asynq · error

Internal

Internal

Error message

cast error: Lua script returned unexpected value: %v

What it means

listMessages runs a Lua script over a Redis list and expects the result to be convertible to []string of message payloads. When cast.ToStringSliceE fails, the raw Lua return value has an unexpected shape, and the library wraps it in an errors.E(errors.Internal, ...) with this message. It indicates a mismatch between what the Lua script returned and what the Go code expects.

Solutions

  1. Check the Redis endpoint: point the client at a plain Redis server that fully supports Lua scripting (not a proxy that strips multi-bulk replies).
  2. Log/capture the `res` value shown in the message to see the actual shape returned and compare with what the Lua script should return.
  3. Verify Redis server version compatibility with the library and flush/repair corrupted queue keys (e.g. inspect keys with the task-key prefix manually).

Example fix

// before
rdb := redis.NewClient(&redis.Options{Addr: proxyAddr}) // proxy mangles Lua replies
infos, err := inspector.ListPending("default")
// after
rdb := redis.NewClient(&redis.Options{Addr: "127.0.0.1:6379"})
if err := rdb.Eval(ctx, "return 1", nil).Err(); err != nil {
    log.Fatal("redis does not support scripting: ", err)
}
infos, err := inspector.ListPending("default")
Defensive patterns

Strategy: try-catch

Validate before calling

if err := rdb.Ping(ctx).Err(); err != nil {
    return err
}
// probe scripting support before listing
if err := rdb.Eval(ctx, "return 1", nil).Err(); err != nil {
    return fmt.Errorf("redis scripting unavailable: %w", err)
}

Type guard

if _, ok := res.([]interface{}); !ok {
    return fmt.Errorf("unexpected Lua reply type %T", res)
}

Try / catch

infos, err := inspector.ListPending(qname)
if err != nil {
    var e *errors.Error
    if errors.As(err, &e) && e.Code == errors.Internal && strings.Contains(err.Error(), "cast error") {
        log.Printf("bad Lua reply for queue %s: %v", qname, err)
        return nil // degrade gracefully
    }
    return err
}

Prevention

When it happens

Trigger: Calling ListPending or ListActive when the Redis Lua script returns a non-slice value (e.g. an error reply, a single scalar, or a nested table) — typically caused by a Redis version whose scripting behavior differs, or corrupted/reshaped keys so the script takes an unexpected branch.

Common situations: Running against an incompatible Redis server (e.g. a proxy or cluster middleware like Twemproxy/Codis that mangles Lua multi-bulk replies); a Redis version upgrade changing reply formatting; manually altered or corrupted task-list keys.

Understand the failure class

Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.

Related errors


AI-assisted analysis of hibiken/asynq@d135f1439b (2026-09-07). Data as JSON: /api/errors/d629b8c73191ce5c. Report an issue: GitHub.

Appendix: source

Thrown at internal/rdb/inspect.go:705

	case base.TaskStateActive:
		key = base.ActiveKey(qname)
	case base.TaskStatePending:
		key = base.PendingKey(qname)
	default:
		panic(fmt.Sprintf("unsupported task state: %v", state))
	}
	// Note: Because we use LPUSH to redis list, we need to calculate the
	// correct range and reverse the list to get the tasks with pagination.
	stop := -pgn.start() - 1
	start := -pgn.stop() - 1
	res, err := listMessagesCmd.Run(context.Background(), r.client,
		[]string{key}, start, stop, base.TaskKeyPrefix(qname)).Result()
	if err != nil {
		return nil, errors.E(errors.Unknown, err)
	}
	data, err := cast.ToStringSliceE(res)
	if err != nil {
		return nil, errors.E(errors.Internal, fmt.Errorf("cast error: Lua script returned unexpected value: %v", res))
	}
	var infos []*base.TaskInfo
	for i := 0; i < len(data); i += 2 {
		m, err := base.DecodeMessage([]byte(data[i]))
		if err != nil {
			continue // bad data, ignore and continue
		}
		var res []byte
		if len(data[i+1]) > 0 {
			res = []byte(data[i+1])
		}
		var nextProcessAt time.Time
		if state == base.TaskStatePending {
			nextProcessAt = r.clock.Now()
		}
		infos = append(infos, &base.TaskInfo{
			Message:       m,
			State:         state,

View on GitHub (pinned to d135f1439b)