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
- 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).
- Log/capture the `res` value shown in the message to see the actual shape returned and compare with what the Lua script should return.
- 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
- Avoid Redis proxies (twemproxy, some cluster shims) that break Lua multi-bulk replies.
- Pin a Redis server version compatible with the library.
- Monitor queue keys for corruption and test List* endpoints after Redis upgrades.
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
- task already exists
- task ID conflicts with another task
- queue not found
- queue is not empty
- task not found
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)